diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..58d7c0e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,31 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build + run: make ast + + - name: Test + run: go test -v ./... + + - name: Benchmark + run: go test -bench ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..766688e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +# Uncomment the following to let goreleaser automatically +# create a GitHub release when a tag is pushed. +# permissions: +# contents: write + +on: + push: + branches-ignore: + - '**' + tags: + - 'v*.*.*' + # to be used by fork patch-releases ^^ + - 'v*.*.*-*' + workflow_dispatch: + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: dockerhub-login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB }} + password: ${{ secrets.DOCKERHUB_KEY }} + + - name: Prepare + id: prepare + run: | + TAG=${GITHUB_REF#refs/tags/} + echo ::set-output name=tag_name::${TAG} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - run: echo ${{ steps.prepare.outputs.tag_name }} + + - name: Run GoReleaser + run: | + make release + docker images + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + VERSION: ${{ steps.prepare.outputs.tag_name }} + DOCKER_USERNAME: ${{ secrets.DOCKERHUB }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..701c594 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# +build/ +.idea + +#vscode +.vscode/ + +# utests +modules/rawdb/mdbx.db/ + +# mobile outputs +mobile/ + +.DS_Store +ast + +genesis*.json + +./deployments/ \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..1d57f04 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,99 @@ +project_name: ast + +release: + disable: false + draft: true + prerelease: auto + +builds: + - id: darwin-amd64 + main: ./cmd/ast + binary: astranet + goos: [ darwin ] + goarch: [ amd64 ] + env: + - CC=o64-clang + - CXX=o64-clang++ + tags: [ nosqlite, noboltdb ] + ldflags: -s -w + + - id: darwin-arm64 + main: ./cmd/ast + binary: astranet + goos: [ darwin ] + goarch: [ arm64 ] + env: + - CC=oa64-clang + - CXX=oa64-clang++ + tags: [ nosqlite, noboltdb ] + ldflags: -s -w + + - id: linux-amd64 + main: ./cmd/ast + binary: astranet + goos: [ linux ] + goarch: [ amd64 ] + env: + - CC=x86_64-linux-gnu-gcc + - CXX=x86_64-linux-gnu-g++ + tags: [ nosqlite, noboltdb ] + ldflags: -s -w -extldflags "-static" # We need to build a static binary because we are building in a glibc based system and running in a musl container + + - id: linux-arm64 + main: ./cmd/ast + binary: astranet + goos: [ linux ] + goarch: [ arm64 ] + env: + - CC=aarch64-linux-gnu-gcc + - CXX=aarch64-linux-gnu-g++ + tags: [ nosqlite, noboltdb ] + ldflags: -s -w -extldflags "-static" # We need to build a static binary because we are building in a glibc based system and running in a musl container + + - id: windows-amd64 + main: ./cmd/ast + binary: astranet + goos: [ windows ] + goarch: [ amd64 ] + env: + - CC=x86_64-w64-mingw32-gcc + - CXX=x86_64-w64-mingw32-g++ + tags: [ nosqlite, noboltdb ] + ldflags: -s -w + + +dockers: + - image_templates: + - astranet/{{ .ProjectName }}:{{ .Version }}-amd64 + dockerfile: Dockerfile.release + use: buildx + skip_push: true + goarch: amd64 + ids: + - linux-amd64 + build_flag_templates: + - --platform=linux/amd64 + + - image_templates: + - astranet/{{ .ProjectName }}:{{ .Version }}-arm64 + dockerfile: Dockerfile.release + skip_push: true + use: buildx + goarch: arm64 + ids: + - linux-arm64 + build_flag_templates: + - --platform=linux/arm64/v8 + +docker_manifests: + - name_template: astranet/{{ .ProjectName }}:{{ .Version }} + skip_push: true + image_templates: + - astranet/{{ .ProjectName }}:{{ .Version }}-amd64 + - astranet/{{ .ProjectName }}:{{ .Version }}-arm64 + + - name_template: astranet/{{ .ProjectName }}:latest + skip_push: true + image_templates: + - astranet/{{ .ProjectName }}:{{ .Version }}-amd64 + - astranet/{{ .ProjectName }}:{{ .Version }}-arm64 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..16653ae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Build +FROM golang:1.19-alpine3.15 AS builder + +# RUN apk add --no-cache gcc musl-dev linux-headers git make +RUN apk add --no-cache build-base linux-headers git bash ca-certificates libstdc++ + +WORKDIR /ast +ADD . . +ENV GO111MODULE="on" +RUN go mod tidy && go build -o ./build/bin/astranet ./cmd/ast + + +FROM alpine:3.15 +#libstdc++ +RUN apk add --no-cache ca-certificates curl tzdata +# copy compiled artifacts from builder +COPY --from=builder /ast/build/bin/* /usr/local/bin/ + +# Setup user and group +# +# from the perspective of the container, uid=1000, gid=1000 is a sensible choice, but if caller creates a .env +# (example in repo root), these defaults will get overridden when make calls docker-compose +ARG UID=1000 +ARG GID=1000 +RUN adduser -D -u $UID -g $GID ast + + +ENV astDATA /home/ast/data +# this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values) +RUN mkdir -p "$astDATA" && chown -R ast:ast "$astDATA" && chmod 777 "$astDATA" +VOLUME /home/ast/data + +USER ast +WORKDIR /home/ast + +RUN echo $UID + +EXPOSE 20012 20013 20014 61015/udp 61016 6060 +ENTRYPOINT ["astranet"] diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 0000000..b17f3af --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,21 @@ +FROM alpine:3.15 + +RUN apk add --no-cache ca-certificates curl tzdata + +COPY astranet /usr/local/bin/ + +ARG UID=1000 +ARG GID=1000 +RUN adduser -D -u $UID -g $GID ast + + +ENV astDATA /home/ast/data +# this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values) +RUN mkdir -p "$astDATA" && chown -R ast:ast "$astDATA" && chmod 777 "$astDATA" +VOLUME /home/ast/data + +USER ast +WORKDIR /home/ast + +EXPOSE 20012 20013 20014 61015/udp 61016 6060 +ENTRYPOINT ["astranet"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0657503 --- /dev/null +++ b/Makefile @@ -0,0 +1,147 @@ +BUILD_TIME := $(shell date +"%Y-%m-%d %H:%M:%S") +#GIT_COMMIT := $(shell git show -s --pretty=format:%h) +GO_VERSION := $(shell go version) +BUILD_PATH := ./build/bin/ +APP_NAME := astranet +APP_PATH := ./cmd/ast +SHELL := /bin/bash +GO = go +#LDFLAGS := -ldflags "-w -s -X github.com/astranetworld/ast/version.BuildNumber=${GIT_COMMIT} -X 'github.com/astranetworld/ast/version.BuildTime=${BUILD_TIME}' -X 'github.com/astranetworld/ast/version.GoVersion=${GO_VERSION}'" + + +# Variables below for building on host OS, and are ignored for docker +# +# Pipe error below to /dev/null since Makefile structure kind of expects +# Go to be available, but with docker it's not strictly necessary +CGO_CFLAGS := $(shell $(GO) env CGO_CFLAGS 2>/dev/null) # don't lose default +CGO_CFLAGS += -DMDBX_FORCE_ASSERTIONS=0 # Enable MDBX's asserts by default in 'devel' branch and disable in releases +#CGO_CFLAGS += -DMDBX_DISABLE_VALIDATION=1 # This feature is not ready yet +#CGO_CFLAGS += -DMDBX_ENABLE_PROFGC=0 # Disabled by default, but may be useful for performance debugging +#CGO_CFLAGS += -DMDBX_ENABLE_PGOP_STAT=0 # Disabled by default, but may be useful for performance debugging +#CGO_CFLAGS += -DMDBX_ENV_CHECKPID=0 # Erigon doesn't do fork() syscall +CGO_CFLAGS += -O +CGO_CFLAGS += -D__BLST_PORTABLE__ +CGO_CFLAGS += -Wno-unknown-warning-option -Wno-enum-int-mismatch -Wno-strict-prototypes +#CGO_CFLAGS += -Wno-error=strict-prototypes + +GIT_COMMIT ?= $(shell git rev-list -1 HEAD) +GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) +GIT_TAG ?= $(shell git describe --tags '--match=v*' --dirty) +PACKAGE = github.com/astranetworld/ast + +BUILD_TAGS = nosqlite,noboltdb +GO_FLAGS += -trimpath -tags $(BUILD_TAGS) -buildvcs=false +GO_FLAGS += -ldflags "-X ${PACKAGE}/params.GitCommit=${GIT_COMMIT} -X ${PACKAGE}/params.GitBranch=${GIT_BRANCH} -X ${PACKAGE}/params.GitTag=${GIT_TAG}" +GOBUILD = CGO_CFLAGS="$(CGO_CFLAGS)" go build -v $(GO_FLAGS) + + +# if using volume-mounting data dir, then must exist on host OS +DOCKER_UID ?= $(shell id -u) +DOCKER_GID ?= $(shell id -g) + + +# == mobiles +#OSFLAG=$(shell uname -sm) + +ANDROID_SDK=$(ANDROID_HOME) +NDK_VERSION=25.2.9519653 +NDK_HOME=$(ANDROID_SDK)/ndk/$(NDK_VERSION) +#ANDROID_SDK=/Users/mac/Library/Android/sdk +MOBILE_GO_FLAGS = -ldflags "-X ${PACKAGE}/cmd/evmsdk/common.VERSION=${GIT_COMMIT}" +MOBILE_PACKAGE= $(shell pwd)/cmd/evmsdk +BUILD_MOBILE_PATH = ./build/mobile/ + + +# --build-arg UID=${DOCKER_UID} --build-arg GID=${DOCKER_GID} + +## go-version: print and verify go version +go-version: + @if [ $(shell go version | cut -c 16-17) -lt 18 ]; then \ + echo "minimum required Golang version is 1.18"; \ + exit 1 ;\ + fi +gen: + @echo "Generate go code ..." + go generate ./... + @echo "Generate done!" +deps: go-version + @echo "setup go deps..." + go mod tidy + @echo "deps done!" + +ast: deps + @echo "start build $(APP_NAME)..." + #go build -v ${LDFLAGS} -o $(BUILD_PATH)$(APP_NAME) ${APP_PATH} + $(GOBUILD) -o $(BUILD_PATH)$(APP_NAME) ${APP_PATH} + @echo "Compile done!" + +images: + @echo "docker images build ..." + DOCKER_BUILDKIT=1 docker build -t astranet/ast:local . + @echo "Compile done!" + +up: + @echo "docker compose up $(APP_NAME) ..." + docker-compose --project-name $(APP_NAME) up -d + docker-compose --project-name $(APP_NAME) logs -f +down: + @echo "docker compose down $(APP_NAME) ..." + docker-compose --project-name $(APP_NAME) down + docker volume ls -q | grep 'astranet' | xargs -I % docker volume rm % + @echo "done!" +stop: + @echo "docker compose stop $(APP_NAME) ..." + docker-compose --project-name $(APP_NAME) stop + @echo "done!" +start: + @echo "docker compose stop $(APP_NAME) ..." + docker-compose --project-name $(APP_NAME) start + docker-compose --project-name $(APP_NAME) logs -f +clean: + go clean + @rm -rf build + +devtools: + env GOBIN= go install github.com/fjl/gencodec@latest + env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest + env GOBIN= go install github.com/prysmaticlabs/fastssz/sszgen@latest + env GOBIN= go install github.com/prysmaticlabs/protoc-gen-go-cast@latest + + +PACKAGE_NAME := github.com/WeAreAmaze/ast +GOLANG_CROSS_VERSION ?= v1.20.7 + +.PHONY: release +release: + @docker run \ + --rm \ + --privileged \ + -e CGO_ENABLED=1 \ + -e GITHUB_TOKEN \ + -e DOCKER_USERNAME \ + -e DOCKER_PASSWORD \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ + --clean --skip-validate + + @docker image push --all-tags astranet/ast + + +#== mobiles start +mobile: clean mobile-dir android ios + +mobile-dir: + #go get golang.org/x/mobile/bind/objc + mkdir -p $(BUILD_MOBILE_PATH)/android +ios: + GOOS=ios CGO_ENABLED=1 GOARCH=arm64 gomobile bind ${MOBILE_GO_FLAGS} -o $(BUILD_MOBILE_PATH)/evmsdk.xcframework -target=ios/arm64 $(MOBILE_PACKAGE) +android: + CGO_ENABLED=1 ANDROID_HOME=$(ANDROID_SDK) ANDROID_NDK_HOME=$(NDK_HOME) gomobile bind -x ${MOBILE_GO_FLAGS} -androidapi 23 -o $(BUILD_MOBILE_PATH)/android/evmsdk.aar -target=android/arm -v $(MOBILE_PACKAGE) +# ANDROID_NDK_CC=$(NDK_HOME)/bin/arm-linux-androideabi-gcc NDK_CC=$(NDK_HOME)/bin/arm-linux-androideabi-gcc +# GOARCH=arm GOOS=android CGO_ENABLED=1 +open-output: + open ./mobile + +#== mobiles end \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..50cf740 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# astranet +astranet(ast) is an implementation of public blockchain (execution client), on the efficiency frontier, written in Go. + +**Disclaimer: this software is currently a tech preview. We will do our best to keep it stable and make no breaking changes, but we don't guarantee anything. Things can and will break.** + +## System Requirements + +* For an Full node :  >=200GB  storage space. + +SSD or NVMe. Do not recommend HDD. + +RAM: >=16GB, 64-bit architecture, [Golang version >= 1.19](https://golang.org/doc/install) + + +## Build from source code +For building the latest alpha release (this will be suitable for most users just wanting to run a node): + +### Most Linux systems and macOS + +ast is written in Go, so building from source code requires the most recent version of Go to be installed. +Instructions for installing Go are available at the [Go installation page](https://golang.org/doc/install) and necessary bundles can be downloaded from the [Go download page](https://golang.org/dl/). +And the repository should be cloned to a local repository. Then, the command make ast configures everything for a temporary build and cleans up afterwards. This method of building only works on UNIX-like operating systems +```sh +git clone https://github.com/astranetworld/ast.git +cd ast +make ast +./build/bin/ast +``` +### Windows + +Windows users may run ast in 3 possible ways: + +* Build executable binaries natively for Windows using [Chocolatey package manager](https://chocolatey.org/) +* Use Docker : see [docker-compose.yml](./docker-compose.yml) +* Use WSL (Windows Subsystem for Linux) **strictly on version 2**. Under this option you can build ast just as you would on a regular Linux distribution. You can point your data also to any of the mounted Windows partitions (eg. `/mnt/c/[...]`, `/mnt/d/[...]` etc) but in such case be advised performance is impacted: this is due to the fact those mount points use `DrvFS` which is a [network file system](#blocks-execution-is-slow-on-cloud-network-drives) and, additionally, MDBX locks the db for exclusive access which implies only one process at a time can access data. This has consequences on the running of `rpcdaemon` which has to be configured as [Remote DB](#for-remote-db) even if it is executed on the very same computer. If instead your data is hosted on the native Linux filesystem non limitations apply. **Please also note the default WSL2 environment has its own IP address which does not match the one of the network interface of Windows host: take this into account when configuring NAT for port 30303 on your router.** + + +### Docker container +Docker allows for building and running ast via containers. This alleviates the need for installing build dependencies onto the host OS. +see [docker-compose.yml](./docker-compose.yml) [dockerfile](./Dockerfile). +For convenience we provide the following commands: +```sh +make images # build docker images than contain executable ast binaries +make up # alias for docker-compose up -d && docker-compose logs -f +make down # alias for docker-compose down && clean docker data +make start # alias for docker-compose start && docker-compose logs -f +make stop # alias for docker-compose stop +``` + +## Executables + +The astranet project comes with one wrappers/executables found in the `cmd` +directory. + +| Command | Description | +| :-----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`astranet`** | Our main astranet CLI client. It can be used by other processes as a gateway into the astranet network via JSON RPC endpoints exposed on top of HTTP transports. `astranet --help` for command line options. | + + +## ast ports + +| Port | Protocol | Purpose | Expose | +|:-----:|:--------:|:------------------------:|:------------------:| +| 61015 | UDP | The port used by discv5. | Public | +| 61016 | TCP | The port used by libp2p. | Public | +| 20012 | TCP | Json rpc/HTTP | Public | +| 20013 | TCP | Json rpc/Websocket | Public | +| 20014 | TCP | Json rpc/HTTP/Websocket | JWT Authentication | +| 4000 | TCP | BlockChain Explorer | Public | +| 6060 | TCP | Metrics | Private | +| 6060 | TCP | Pprof | Private | + +## License +The astranet library is licensed under the +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1a7688c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +|---------|--------------------| +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Audit reports + +Audit reports are published in the `docs` folder + +| Scope | Date | Report Link | +|-------|------|-------------| + +## Reporting a Vulnerability + +**Please do not file a public ticket** mentioning the vulnerability. + +To find out how to disclose a vulnerability in astranet email support@astranet.com. Please read the [disclosure page] for more information about publicly disclosed security vulnerabilities. + +Use the built-in `ast version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`] file which contains known security vulnerabilities concerning `ast`, and cross-check the data against its own version number. + +The following key may be used to communicate sensitive information to developers. + +Fingerprint: + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: pgp.mit.edu diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go new file mode 100644 index 0000000..1e15b51 --- /dev/null +++ b/accounts/abi/abi.go @@ -0,0 +1,254 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "io" +) + +// The ABI holds information about a contract's context and available +// invokable methods. It will allow you to type check function calls and +// packs data accordingly. +type ABI struct { + Constructor Method + Methods map[string]Method + Events map[string]Event + Errors map[string]Error + + // Additional "special" functions introduced in solidity v0.6.0. + // It's separated from the original default fallback. Each contract + // can only define one fallback and receive function. + Fallback Method // Note it's also used to represent legacy fallback before v0.6.0 + Receive Method +} + +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + return abi, nil +} + +// Pack the given method name to conform the ABI. Method call's data +// will consist of method_id, args0, arg1, ... argN. Method id consists +// of 4 bytes and arguments are all 32 bytes. +// Method ids are created from the first 4 bytes of the hash of the +// methods string signature. (signature = baz(uint32,string32)) +func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { + // Fetch the ABI of the requested method + if name == "" { + // constructor + arguments, err := abi.Constructor.Inputs.Pack(args...) + if err != nil { + return nil, err + } + return arguments, nil + } + method, exist := abi.Methods[name] + if !exist { + return nil, fmt.Errorf("method '%s' not found", name) + } + arguments, err := method.Inputs.Pack(args...) + if err != nil { + return nil, err + } + // Pack up the method ID too if not a constructor and return + return append(method.ID, arguments...), nil +} + +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + var args Arguments + if method, ok := abi.Methods[name]; ok { + if len(data)%32 != 0 { + return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data) + } + args = method.Outputs + } + if event, ok := abi.Events[name]; ok { + args = event.Inputs + } + if args == nil { + return nil, fmt.Errorf("abi: could not locate named method or event: %s", name) + } + return args, nil +} + +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err + } + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) +} + +// UnpackIntoMap unpacks a log into the provided map[string]interface{}. +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + return args.UnpackIntoMap(v, data) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (abi *ABI) UnmarshalJSON(data []byte) error { + var fields []struct { + Type string + Name string + Inputs []Argument + Outputs []Argument + + // Status indicator which can be: "pure", "view", + // "nonpayable" or "payable". + StateMutability string + + // Deprecated Status indicators, but removed in v0.6.0. + Constant bool // True if function is either pure or view + Payable bool // True if function is payable + + // Event relevant indicator represents the event is + // declared as anonymous. + Anonymous bool + } + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + abi.Methods = make(map[string]Method) + abi.Events = make(map[string]Event) + abi.Errors = make(map[string]Error) + for _, field := range fields { + switch field.Type { + case "constructor": + abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil) + case "function": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok }) + abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs) + case "fallback": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasFallback() { + return errors.New("only single fallback is allowed") + } + abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "receive": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasReceive() { + return errors.New("only single receive is allowed") + } + if field.StateMutability != "payable" { + return errors.New("the statemutability of receive can only be payable") + } + abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "event": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok }) + abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs) + case "error": + // Errors cannot be overloaded or overridden but are inherited, + // no need to resolve the name conflict here. + abi.Errors[field.Name] = NewError(field.Name, field.Inputs) + default: + return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) + } + } + return nil +} + +// MethodById looks up a method by the 4-byte id, +// returns nil if none found. +func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { + if len(sigdata) < 4 { + return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) + } + for _, method := range abi.Methods { + if bytes.Equal(method.ID, sigdata[:4]) { + return &method, nil + } + } + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) +} + +// EventByID looks an event up by its topic hash in the +// ABI and returns nil if none found. +func (abi *ABI) EventByID(topic types.Hash) (*Event, error) { + for _, event := range abi.Events { + if bytes.Equal(event.ID.Bytes(), topic.Bytes()) { + return &event, nil + } + } + return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) +} + +// HasFallback returns an indicator whether a fallback function is included. +func (abi *ABI) HasFallback() bool { + return abi.Fallback.Type == Fallback +} + +// HasReceive returns an indicator whether a receive function is included. +func (abi *ABI) HasReceive() bool { + return abi.Receive.Type == Receive +} + +// revertSelector is a special function selector for revert reason unpacking. +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +func UnpackRevert(data []byte) (string, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], revertSelector) { + return "", errors.New("invalid data for unpacking") + } + typ, _ := NewType("string", "", nil) + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { + return "", err + } + return unpacked[0].(string), nil +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go new file mode 100644 index 0000000..a437e41 --- /dev/null +++ b/accounts/abi/argument.go @@ -0,0 +1,273 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type + Indexed bool // indexed is only used by events +} + +type Arguments []Argument + +type ArgumentMarshaling struct { + Name string + Type string + InternalType string + Components []ArgumentMarshaling + Indexed bool +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (argument *Argument) UnmarshalJSON(data []byte) error { + var arg ArgumentMarshaling + err := json.Unmarshal(data, &arg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components) + if err != nil { + return err + } + argument.Name = arg.Name + argument.Indexed = arg.Indexed + + return nil +} + +// NonIndexed returns the arguments with indexed arguments filtered out. +func (arguments Arguments) NonIndexed() Arguments { + var ret []Argument + for _, arg := range arguments { + if !arg.Indexed { + ret = append(ret, arg) + } + } + return ret +} + +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]. +func (arguments Arguments) isTuple() bool { + return len(arguments) > 1 +} + +// Unpack performs the operation hexdata -> Go format. +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected") + } + return make([]interface{}, 0), nil + } + return arguments.UnpackValues(data) +} + +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + // Make sure map is not nil + if v == nil { + return errors.New("abi: cannot unpack into a nil map") + } + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to unmarshall an empty string while arguments are expected") + } + return nil // Nothing to unmarshal, return + } + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] + } + return nil +} + +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + if len(values) == 0 { + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to copy no values while arguments are expected") + } + return nil // Nothing to copy, return + } + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) +} + +// unpackAtomic unpacks ( hexdata -> go ) a single value +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { + dst := reflect.ValueOf(v).Elem() + src := reflect.ValueOf(marshalledValues) + + if dst.Kind() == reflect.Struct { + return set(dst.Field(0), src) + } + return set(dst, src) +} + +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { + value := reflect.ValueOf(v).Elem() + nonIndexedArgs := arguments.NonIndexed() + + switch value.Kind() { + case reflect.Struct: + argNames := make([]string, len(nonIndexedArgs)) + for i, arg := range nonIndexedArgs { + argNames[i] = arg.Name + } + var err error + abi2struct, err := mapArgNamesToStructFields(argNames, value) + if err != nil { + return err + } + for i, arg := range nonIndexedArgs { + field := value.FieldByName(abi2struct[arg.Name]) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) + } + if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + case reflect.Slice, reflect.Array: + if value.Len() < len(marshalledValues) { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) + } + for i := range nonIndexedArgs { + if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + default: + return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type()) + } + return nil +} + +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// without supplying a struct to unpack into. Instead, this method returns a list containing the +// values. An atomic argument will be a list with one element. +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { + nonIndexedArgs := arguments.NonIndexed() + retval := make([]interface{}, 0, len(nonIndexedArgs)) + virtualArgs := 0 + for index, arg := range nonIndexedArgs { + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + if err != nil { + return nil, err + } + if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } + retval = append(retval, marshalledValue) + } + return retval, nil +} + +// PackValues performs the operation Go format -> Hexdata. +// It is the semantic opposite of UnpackValues. +func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { + return arguments.Pack(args...) +} + +// Pack performs the operation Go format -> Hexdata. +func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { + // Make sure arguments match up and pack them + abiArgs := arguments + if len(args) != len(abiArgs) { + return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs)) + } + // variable input is the output appended at the end of packed + // output. This is used for strings and bytes types input. + var variableInput []byte + + // input offset is the bytes offset for packed output + inputOffset := 0 + for _, abiArg := range abiArgs { + inputOffset += getTypeSize(abiArg.Type) + } + var ret []byte + for i, a := range args { + input := abiArgs[i] + // pack the input + packed, err := input.Type.pack(reflect.ValueOf(a)) + if err != nil { + return nil, err + } + // check for dynamic types + if isDynamicType(input.Type) { + // set the offset + ret = append(ret, packNum(reflect.ValueOf(inputOffset))...) + // calculate next offset + inputOffset += len(packed) + // append to variable input + variableInput = append(variableInput, packed...) + } else { + // append the packed value to the input + ret = append(ret, packed...) + } + } + // append the variable input at the end of the packed input + ret = append(ret, variableInput...) + + return ret, nil +} + +// ToCamelCase converts an under-score string to a camel-case string +func ToCamelCase(input string) string { + parts := strings.Split(input, "_") + for i, s := range parts { + if len(s) > 0 { + parts[i] = strings.ToUpper(s[:1]) + s[1:] + } + } + return strings.Join(parts, "") +} diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go new file mode 100644 index 0000000..475d2fe --- /dev/null +++ b/accounts/abi/bind/auth.go @@ -0,0 +1,162 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bind + +import ( + "context" + "crypto/ecdsa" + "errors" + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/accounts/keystore" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "io" + "math/big" +) + +// ErrNoChainID is returned whenever the user failed to specify a chain id. +var ErrNoChainID = errors.New("no chain id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + +// NewTransactor is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithChainID instead. +func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID") + json, err := io.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactor(key.PrivateKey), nil +} + +// NewKeyStoreTransactor is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithChainID instead. +func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID") + signer := transaction.HomesteadSigner{} + return &TransactOpts{ + From: account.Address, + Signer: func(address types.Address, tx *transaction.Transaction) (*transaction.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} + +// NewKeyedTransactor is a utility method to easily create a transaction signer +// from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithChainID instead. +func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID") + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := transaction.HomesteadSigner{} + return &TransactOpts{ + From: keyAddr, + Signer: func(address types.Address, tx *transaction.Transaction) (*transaction.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + } +} + +// NewTransactorWithChainID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) { + json, err := io.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) +} + +// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { + if chainID == nil { + return nil, ErrNoChainID + } + signer := transaction.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: account.Address, + Signer: func(address types.Address, tx *transaction.Transaction) (*transaction.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} + +// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + if chainID == nil { + return nil, ErrNoChainID + } + signer := transaction.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address types.Address, tx *transaction.Transaction) (*transaction.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go new file mode 100644 index 0000000..285a60d --- /dev/null +++ b/accounts/abi/bind/backend.go @@ -0,0 +1,127 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bind + +import ( + "context" + "errors" + astranet "github.com/astranetworld/ast" + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + "math/big" +) + +var ( + // ErrNoCode is returned by call and transact operations for which the requested + // recipient contract to operate on does not exist in the state db or does not + // have any code associated with it (i.e. suicided). + ErrNoCode = errors.New("no contract code at given address") + + // ErrNoPendingState is raised when attempting to perform a pending state action + // on a backend that doesn't implement PendingContractCaller. + ErrNoPendingState = errors.New("backend does not support pending state") + + // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves + // an empty contract behind. + ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") +) + +// ContractCaller defines the methods needed to allow operating with a contract on a read +// only basis. +type ContractCaller interface { + // CodeAt returns the code of the given account. This is needed to differentiate + // between contract internal errors and the local chain being out of sync. + CodeAt(ctx context.Context, contract types.Address, blockNumber *uint256.Int) ([]byte, error) + + // CallContract executes an Ethereum contract call with the specified data as the + // input. + CallContract(ctx context.Context, call astranet.CallMsg, blockNumber *uint256.Int) ([]byte, error) +} + +// PendingContractCaller defines methods to perform contract calls on the pending state. +// Call will try to discover this interface when access to the pending state is requested. +// If the backend does not support the pending state, Call returns ErrNoPendingState. +type PendingContractCaller interface { + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, contract types.Address) ([]byte, error) + + // PendingCallContract executes an Ethereum contract call against the pending state. + PendingCallContract(ctx context.Context, call astranet.CallMsg) ([]byte, error) +} + +// ContractTransactor defines the methods needed to allow operating with a contract +// on a write only basis. Besides the transacting method, the remainder are helpers +// used when the user does not provide some needed values, but rather leaves it up +// to the transactor to decide. +type ContractTransactor interface { + // HeaderByNumber returns a block header from the current canonical chain. If + // number is nil, the latest known header is returned. + HeaderByNumber(ctx context.Context, number *uint256.Int) (*block.Header, error) + + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, account types.Address) ([]byte, error) + + // PendingNonceAt retrieves the current pending nonce associated with an account. + PendingNonceAt(ctx context.Context, account types.Address) (uint64, error) + + // SuggestGasPrice retrieves the currently suggested gas price to allow a timely + // execution of a transaction. + SuggestGasPrice(ctx context.Context) (*uint256.Int, error) + + // SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow + // a timely execution of a transaction. + SuggestGasTipCap(ctx context.Context) (*uint256.Int, error) + + // EstimateGas tries to estimate the gas needed to execute a specific + // transaction based on the current pending state of the backend blockchain. + // There is no guarantee that this is the true gas limit requirement as other + // transactions may be added or removed by miners, but it should provide a basis + // for setting a reasonable default. + EstimateGas(ctx context.Context, call astranet.CallMsg) (gas uint64, err error) + + // SendTransaction injects the transaction into the pending pool for execution. + SendTransaction(ctx context.Context, tx *transaction.Transaction) error +} + +// ContractFilterer defines the methods needed to access log events using one-off +// queries or continuous event subscriptions. +type ContractFilterer interface { + // FilterLogs executes a log filter operation, blocking during execution and + // returning all the results in one batch. + // + // TODO(karalabe): Deprecate when the subscription one can return past data too. + FilterLogs(ctx context.Context, query astranet.FilterQuery) ([]block.Log, error) + + // SubscribeFilterLogs creates a background log filtering operation, returning + // a subscription immediately, which can be used to stream the found events. + SubscribeFilterLogs(ctx context.Context, query astranet.FilterQuery, ch chan<- block.Log) (astranet.Subscription, error) +} + +// DeployBackend wraps the operations needed by WaitMined and WaitDeployed. +type DeployBackend interface { + TransactionReceipt(ctx context.Context, txHash types.Hash) (*block.Receipt, error) + CodeAt(ctx context.Context, account types.Address, blockNumber *big.Int) ([]byte, error) +} + +// ContractBackend defines the methods needed to work with contracts on a read-write basis. +type ContractBackend interface { + ContractCaller + ContractTransactor + ContractFilterer +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go new file mode 100644 index 0000000..467b81f --- /dev/null +++ b/accounts/abi/bind/base.go @@ -0,0 +1,532 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bind + +import ( + "context" + "errors" + "fmt" + astranet "github.com/astranetworld/ast" + "github.com/astranetworld/ast/accounts/abi" + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + event "github.com/astranetworld/ast/modules/event/v2" + "github.com/holiman/uint256" + "strings" + "sync" +) + +const basefeeWiggleMultiplier = 2 + +// SignerFn is a signer function callback when a contract requires a method to +// sign the transaction before submission. +type SignerFn func(types.Address, *transaction.Transaction) (*transaction.Transaction, error) + +// CallOpts is the collection of options to fine tune a contract call request. +type CallOpts struct { + Pending bool // Whether to operate on the pending state or the last known one + From types.Address // Optional the sender address, otherwise the first account is used + BlockNumber *uint256.Int // Optional the block number on which the call should be performed + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// TransactOpts is the collection of authorization data required to create a +// valid Ethereum transaction. +type TransactOpts struct { + From types.Address // Ethereum account to send the transaction from + Nonce *uint64 // Nonce to use for the transaction execution (nil = use pending state) + Signer SignerFn // Method to use for signing the transaction (mandatory) + + Value *uint256.Int // Funds to transfer along the transaction (nil = 0 = no funds) + GasPrice *uint256.Int // Gas price to use for the transaction execution (nil = gas price oracle) + GasFeeCap *uint256.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle) + GasTipCap *uint256.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle) + GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + + NoSend bool // Do all transact steps but do not send the transaction +} + +// FilterOpts is the collection of options to fine tune filtering for events +// within a bound contract. +type FilterOpts struct { + Start uint64 // Start of the queried range + End *uint64 // End of the range (nil = latest) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// WatchOpts is the collection of options to fine tune subscribing for events +// within a bound contract. +type WatchOpts struct { + Start *uint64 // Start of the queried range (nil = latest) + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// MetaData collects all metadata for a bound contract. +type MetaData struct { + mu sync.Mutex + Sigs map[string]string + Bin string + ABI string + ab *abi.ABI +} + +func (m *MetaData) GetAbi() (*abi.ABI, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.ab != nil { + return m.ab, nil + } + if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil { + return nil, err + } else { + m.ab = &parsed + } + return m.ab, nil +} + +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + address types.Address // Deployment address of the contract on the Ethereum blockchain + abi abi.ABI // Reflect based ABI to access the correct Ethereum methods + caller ContractCaller // Read interface to interact with the blockchain + transactor ContractTransactor // Write interface to interact with the blockchain + filterer ContractFilterer // Event filtering to interact with the blockchain +} + +// NewBoundContract creates a low level contract interface through which calls +// and transactions may be made through. +func NewBoundContract(address types.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { + return &BoundContract{ + address: address, + abi: abi, + caller: caller, + transactor: transactor, + filterer: filterer, + } +} + +// DeployContract deploys a contract onto the Ethereum blockchain and binds the +// deployment address with a Go wrapper. +func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (types.Address, *transaction.Transaction, *BoundContract, error) { + // Otherwise try to deploy the contract + c := NewBoundContract(types.Address{}, abi, backend, backend, backend) + + input, err := c.abi.Pack("", params...) + if err != nil { + return types.Address{}, nil, nil, err + } + tx, err := c.transact(opts, nil, append(bytecode, input...)) + if err != nil { + return types.Address{}, nil, nil, err + } + c.address = crypto.CreateAddress(opts.From, tx.Nonce()) + return c.address, tx, c, nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } + if results == nil { + results = new([]interface{}) + } + // Pack the input, call and unpack the results + input, err := c.abi.Pack(method, params...) + if err != nil { + return err + } + var ( + msg = astranet.CallMsg{From: opts.From, To: &c.address, Data: input} + ctx = ensureContext(opts.Context) + code []byte + output []byte + ) + if opts.Pending { + pb, ok := c.caller.(PendingContractCaller) + if !ok { + return ErrNoPendingState + } + output, err = pb.PendingCallContract(ctx, msg) + if err != nil { + return err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } + } else { + output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) + if err != nil { + return err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } + } + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res + return err + } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) +} + +// Transact invokes the (paid) contract method with params as input values. +func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*transaction.Transaction, error) { + // Otherwise pack up the parameters and invoke the contract + input, err := c.abi.Pack(method, params...) + if err != nil { + return nil, err + } + // todo(rjl493456442) check the method is payable or not, + // reject invalid transaction at the first place + return c.transact(opts, &c.address, input) +} + +// RawTransact initiates a transaction with the given raw calldata as the input. +// It's usually used to initiate transactions for invoking **Fallback** function. +func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*transaction.Transaction, error) { + // todo(rjl493456442) check the method is payable or not, + // reject invalid transaction at the first place + return c.transact(opts, &c.address, calldata) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (c *BoundContract) Transfer(opts *TransactOpts) (*transaction.Transaction, error) { + // todo(rjl493456442) check the payable fallback or receive is defined + // or not, reject invalid transaction at the first place + return c.transact(opts, &c.address, nil) +} + +func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *types.Address, input []byte, head *block.Header) (*transaction.Transaction, error) { + // Normalize value + value := opts.Value + if value == nil { + value = uint256.NewInt(0) + } + // Estimate TipCap + gasTipCap := opts.GasTipCap + if gasTipCap == nil { + tip, err := c.transactor.SuggestGasTipCap(ensureContext(opts.Context)) + if err != nil { + return nil, err + } + gasTipCap = tip + } + // Estimate FeeCap + gasFeeCap := opts.GasFeeCap + if gasFeeCap == nil { + gasFeeCap = gasTipCap.Add(gasTipCap, head.BaseFee.Mod(head.BaseFee, uint256.NewInt(basefeeWiggleMultiplier))) + } + if gasFeeCap.Cmp(gasTipCap) < 0 { + return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap) + } + // Estimate GasLimit + gasLimit := opts.GasLimit + if opts.GasLimit == 0 { + var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, uint256.NewInt(0), gasTipCap, gasFeeCap, value) + if err != nil { + return nil, err + } + } + // create the transaction + nonce, err := c.getNonce(opts) + if err != nil { + return nil, err + } + baseTx := &transaction.DynamicFeeTx{ + To: contract, + Nonce: nonce, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Gas: gasLimit, + Value: value, + Data: input, + } + return transaction.NewTx(baseTx), nil +} + +func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *types.Address, input []byte) (*transaction.Transaction, error) { + if opts.GasFeeCap != nil || opts.GasTipCap != nil { + return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") + } + // Normalize value + value := opts.Value + if value == nil { + value = uint256.NewInt(0) + } + // Estimate GasPrice + gasPrice := opts.GasPrice + if gasPrice == nil { + price, err := c.transactor.SuggestGasPrice(ensureContext(opts.Context)) + if err != nil { + return nil, err + } + gasPrice = price + } + // Estimate GasLimit + gasLimit := opts.GasLimit + if opts.GasLimit == 0 { + var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, uint256.NewInt(0), uint256.NewInt(0), value) + if err != nil { + return nil, err + } + } + // create the transaction + nonce, err := c.getNonce(opts) + if err != nil { + return nil, err + } + baseTx := &transaction.LegacyTx{ + To: contract, + Nonce: nonce, + GasPrice: gasPrice, + Gas: gasLimit, + Value: value, + Data: input, + } + return transaction.NewTx(baseTx), nil +} + +func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *types.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *uint256.Int) (uint64, error) { + if contract != nil { + // Gas estimation cannot succeed without code for method invocations. + if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { + return 0, err + } else if len(code) == 0 { + return 0, ErrNoCode + } + } + msg := astranet.CallMsg{ + From: opts.From, + To: contract, + GasPrice: gasPrice, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Value: value, + Data: input, + } + return c.transactor.EstimateGas(ensureContext(opts.Context), msg) +} + +func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { + if opts.Nonce == nil { + return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) + } else { + return *opts.Nonce, nil + } +} + +// transact executes an actual transaction invocation, first deriving any missing +// authorization fields, and then scheduling the transaction for execution. +func (c *BoundContract) transact(opts *TransactOpts, contract *types.Address, input []byte) (*transaction.Transaction, error) { + if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { + return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + // Create the transaction + var ( + rawTx *transaction.Transaction + err error + ) + if opts.GasPrice != nil { + rawTx, err = c.createLegacyTx(opts, contract, input) + } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, nil) + } else { + + // Only query for basefee if gasPrice not specified + if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), uint256.NewInt(0) /* latest */); errHead != nil { + return nil, errHead + } else if head.BaseFee != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, head) + } else { + // Chain is not London ready -> use legacy transaction + rawTx, err = c.createLegacyTx(opts, contract, input) + } + } + if err != nil { + return nil, err + } + // Sign the transaction and schedule it for execution + if opts.Signer == nil { + return nil, errors.New("no signer to authorize the transaction with") + } + signedTx, err := opts.Signer(opts.From, rawTx) + if err != nil { + return nil, err + } + if opts.NoSend { + return signedTx, nil + } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + return nil, err + } + return signedTx, nil +} + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan block.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(FilterOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan block.Log, 128) + + config := astranet.FilterQuery{ + Addresses: []types.Address{c.address}, + Topics: topics, + FromBlock: uint256.NewInt(opts.Start), + } + if opts.End != nil { + config.ToBlock = uint256.NewInt(*opts.End) + } + /* TODO(karalabe): Replace the rest of the method below with this when supported + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + */ + buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) + if err != nil { + return nil, nil, err + } + sub, err := event.NewSubscription(func(quit <-chan struct{}) error { + for _, log := range buff { + select { + case logs <- log: + case <-quit: + return nil + } + } + return nil + }), nil + + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// WatchLogs filters subscribes to contract logs for future blocks, returning a +// subscription object that can be used to tear down the watcher. +func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan block.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(WatchOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan block.Log, 128) + + config := astranet.FilterQuery{ + Addresses: []types.Address{c.address}, + Topics: topics, + } + if opts.Start != nil { + config.FromBlock = uint256.NewInt(*opts.Start) + } + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// UnpackLog unpacks a retrieved log into the provided output structure. +func (c *BoundContract) UnpackLog(out interface{}, event string, log block.Log) error { + if log.Topics[0] != c.abi.Events[event].ID { + return fmt.Errorf("event signature mismatch") + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopics(out, indexed, log.Topics[1:]) +} + +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log block.Log) error { + if log.Topics[0] != c.abi.Events[event].ID { + return fmt.Errorf("event signature mismatch") + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + +// ensureContext is a helper method to ensure a context is not nil, even if the +// user specified it as such. +func ensureContext(ctx context.Context) context.Context { + if ctx == nil { + return context.Background() + } + return ctx +} diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go new file mode 100644 index 0000000..5228c54 --- /dev/null +++ b/accounts/abi/bind/bind.go @@ -0,0 +1,648 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "bytes" + "errors" + "fmt" + "github.com/astranetworld/ast/accounts/abi" + "github.com/astranetworld/ast/log" + "go/format" + "regexp" + "strings" + "text/template" + "unicode" +) + +// Lang is a target programming language selector to generate bindings for. +type Lang int + +const ( + LangGo Lang = iota + LangJava + LangObjC +) + +func isKeyWord(arg string) bool { + switch arg { + case "break": + case "case": + case "chan": + case "const": + case "continue": + case "default": + case "defer": + case "else": + case "fallthrough": + case "for": + case "func": + case "go": + case "goto": + case "if": + case "import": + case "interface": + case "iota": + case "map": + case "make": + case "new": + case "package": + case "range": + case "return": + case "select": + case "struct": + case "switch": + case "type": + case "var": + default: + return false + } + + return true +} + +// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant +// to be used as is in client code, but rather as an intermediate struct which +// enforces compile time type safety and naming convention opposed to having to +// manually maintain hard coded strings that break on runtime. +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { + var ( + // contracts is the map of each individual contract requested binding + contracts = make(map[string]*tmplContract) + + // structs is the map of all redeclared structs shared by passed contracts. + structs = make(map[string]*tmplStruct) + + // isLib is the map used to flag each encountered library as such + isLib = make(map[string]struct{}) + ) + for i := 0; i < len(types); i++ { + // Parse the actual ABI to generate the binding for + evmABI, err := abi.JSON(strings.NewReader(abis[i])) + if err != nil { + return "", err + } + // Strip any whitespace from the JSON ABI + strippedABI := strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, abis[i]) + + // Extract the call and transact methods; events, struct definitions; and sort them alphabetically + var ( + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) + events = make(map[string]*tmplEvent) + fallback *tmplMethod + receive *tmplMethod + + // identifiers are used to detect duplicated identifiers of functions + // and events. For all calls, transacts and events, abigen will generate + // corresponding bindings. However we have to ensure there is no + // identifier collisions in the bindings of these categories. + callIdentifiers = make(map[string]bool) + transactIdentifiers = make(map[string]bool) + eventIdentifiers = make(map[string]bool) + ) + + for _, input := range evmABI.Constructor.Inputs { + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + + for _, original := range evmABI.Methods { + // Normalize the method for capital cases and non-anonymous inputs/outputs + normalized := original + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + + // Ensure there is no duplicated identifier + var identifiers = callIdentifiers + if !original.IsConstant() { + identifiers = transactIdentifiers + } + if identifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + identifiers[normalizedName] = true + + normalized.Name = normalizedName + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" || isKeyWord(input.Name) { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + normalized.Outputs = make([]abi.Argument, len(original.Outputs)) + copy(normalized.Outputs, original.Outputs) + for j, output := range normalized.Outputs { + if output.Name != "" { + normalized.Outputs[j].Name = capitalise(output.Name) + } + if hasStruct(output.Type) { + bindStructType[lang](output.Type, structs) + } + } + // Append the methods to the call or transact lists + if original.IsConstant() { + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } else { + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } + } + for _, original := range evmABI.Events { + // Skip anonymous events as they don't support explicit filtering + if original.Anonymous { + continue + } + // Normalize the event for capital cases and non-anonymous outputs + normalized := original + + // Ensure there is no duplicated identifier + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + if eventIdentifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + eventIdentifiers[normalizedName] = true + normalized.Name = normalizedName + + used := make(map[string]bool) + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" || isKeyWord(input.Name) { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + // Event is a bit special, we need to define event struct in binding, + // ensure there is no camel-case-style name conflict. + for index := 0; ; index++ { + if !used[capitalise(normalized.Inputs[j].Name)] { + used[capitalise(normalized.Inputs[j].Name)] = true + break + } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + // Append the event to the accumulator list + events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} + } + // Add two special fallback functions if they exist + if evmABI.HasFallback() { + fallback = &tmplMethod{Original: evmABI.Fallback} + } + if evmABI.HasReceive() { + receive = &tmplMethod{Original: evmABI.Receive} + } + // There is no easy way to pass arbitrary java objects to the Go side. + if len(structs) > 0 && lang == LangJava { + return "", errors.New("java binding for tuple arguments is not supported yet") + } + + contracts[types[i]] = &tmplContract{ + Type: capitalise(types[i]), + InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), + InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), + Constructor: evmABI.Constructor, + Calls: calls, + Transacts: transacts, + Fallback: fallback, + Receive: receive, + Events: events, + Libraries: make(map[string]string), + } + // Function 4-byte signatures are stored in the same sequence + // as types, if available. + if len(fsigs) > i { + contracts[types[i]].FuncSigs = fsigs[i] + } + // Parse library references. + for pattern, name := range libs { + matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) + if err != nil { + log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) + } + if matched { + contracts[types[i]].Libraries[pattern] = name + // keep track that this type is a library + if _, ok := isLib[name]; !ok { + isLib[name] = struct{}{} + } + } + } + } + // Check if that type has already been identified as a library + for i := 0; i < len(types); i++ { + _, ok := isLib[types[i]] + contracts[types[i]].Library = ok + } + // Generate the contract template data content and render it + data := &tmplData{ + Package: pkg, + Contracts: contracts, + Libraries: libs, + Structs: structs, + } + buffer := new(bytes.Buffer) + + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +// bindType is a set of type binders that convert Solidity types to some supported +// programming language types. +var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTypeGo, + LangJava: bindTypeJava, +} + +// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. +func bindBasicTypeGo(kind abi.Type) string { + switch kind.T { + case abi.AddressTy: + return "common.Address" + case abi.IntTy, abi.UintTy: + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + switch parts[2] { + case "8", "16", "32", "64": + return fmt.Sprintf("%sint%s", parts[1], parts[2]) + } + return "*big.Int" + case abi.FixedBytesTy: + return fmt.Sprintf("[%d]byte", kind.Size) + case abi.BytesTy: + return "[]byte" + case abi.FunctionTy: + return "[24]byte" + default: + // string, bool types + return kind.String() + } +} + +// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping +// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + case abi.SliceTy: + return "[]" + bindTypeGo(*kind.Elem, structs) + default: + return bindBasicTypeGo(kind) + } +} + +// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones. +func bindBasicTypeJava(kind abi.Type) string { + switch kind.T { + case abi.AddressTy: + return "Address" + case abi.IntTy, abi.UintTy: + // Note that uint and int (without digits) are also matched, + // these are size 256, and will translate to BigInt (the default). + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + if len(parts) != 3 { + return kind.String() + } + // All unsigned integers should be translated to BigInt since gomobile doesn't + // support them. + if parts[1] == "u" { + return "BigInt" + } + + namedSize := map[string]string{ + "8": "byte", + "16": "short", + "32": "int", + "64": "long", + }[parts[2]] + + // default to BigInt + if namedSize == "" { + namedSize = "BigInt" + } + return namedSize + case abi.FixedBytesTy, abi.BytesTy: + return "byte[]" + case abi.BoolTy: + return "boolean" + case abi.StringTy: + return "String" + case abi.FunctionTy: + return "byte[24]" + default: + return kind.String() + } +} + +// pluralizeJavaType explicitly converts multidimensional types to predefined +// types in go side. +func pluralizeJavaType(typ string) string { + switch typ { + case "boolean": + return "Bools" + case "String": + return "Strings" + case "Address": + return "Addresses" + case "byte[]": + return "Binaries" + case "BigInt": + return "BigInts" + } + return typ + "[]" +} + +// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping +// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + case abi.ArrayTy, abi.SliceTy: + return pluralizeJavaType(bindTypeJava(*kind.Elem, structs)) + default: + return bindBasicTypeJava(kind) + } +} + +// bindTopicType is a set of type binders that convert Solidity types to some +// supported programming language topic types. +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTopicTypeGo, + LangJava: bindTopicTypeJava, +} + +// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same +// functionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindTypeGo(kind, structs) + + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert stringS and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + if bound == "string" || bound == "[]byte" { + bound = "common.Hash" + } + return bound +} + +// bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same +// functionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindTypeJava(kind, structs) + + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert strings and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + if bound == "String" || bound == "byte[]" { + bound = "Hash" + } + return bound +} + +// bindStructType is a set of type binders that convert Solidity tuple types to some supported +// programming language struct definition. +var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindStructTypeGo, + LangJava: bindStructTypeJava, +} + +// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping +// in the given map. +// Notably, this function will resolve and record nested struct recursively. +func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + // We compose a raw struct name and a canonical parameter expression + // together here. The reason is before solidity v0.5.11, kind.TupleRawName + // is empty, so we use canonical parameter expression to distinguish + // different struct definition. From the consideration of backward + // compatibility, we concat these two together so that if kind.TupleRawName + // is not empty, it can have unique id. + id := kind.TupleRawName + kind.String() + if s, exist := structs[id]; exist { + return s.Name + } + var ( + names = make(map[string]bool) + fields []*tmplField + ) + for i, elem := range kind.TupleElems { + name := capitalise(kind.TupleRawNames[i]) + name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) + names[name] = true + fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) + } + name := kind.TupleRawName + if name == "" { + name = fmt.Sprintf("Struct%d", len(structs)) + } + name = capitalise(name) + + structs[id] = &tmplStruct{ + Name: name, + Fields: fields, + } + return name + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs) + case abi.SliceTy: + return "[]" + bindStructTypeGo(*kind.Elem, structs) + default: + return bindBasicTypeGo(kind) + } +} + +// bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping +// in the given map. +// Notably, this function will resolve and record nested struct recursively. +func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + // We compose a raw struct name and a canonical parameter expression + // together here. The reason is before solidity v0.5.11, kind.TupleRawName + // is empty, so we use canonical parameter expression to distinguish + // different struct definition. From the consideration of backward + // compatibility, we concat these two together so that if kind.TupleRawName + // is not empty, it can have unique id. + id := kind.TupleRawName + kind.String() + if s, exist := structs[id]; exist { + return s.Name + } + var fields []*tmplField + for i, elem := range kind.TupleElems { + field := bindStructTypeJava(*elem, structs) + fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem}) + } + name := kind.TupleRawName + if name == "" { + name = fmt.Sprintf("Class%d", len(structs)) + } + structs[id] = &tmplStruct{ + Name: name, + Fields: fields, + } + return name + case abi.ArrayTy, abi.SliceTy: + return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs)) + default: + return bindBasicTypeJava(kind) + } +} + +// namedType is a set of functions that transform language specific types to +// named versions that may be used inside method names. +var namedType = map[Lang]func(string, abi.Type) string{ + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, + LangJava: namedTypeJava, +} + +// namedTypeJava converts some primitive data types to named variants that can +// be used as parts of method names. +func namedTypeJava(javaKind string, solKind abi.Type) string { + switch javaKind { + case "byte[]": + return "Binary" + case "boolean": + return "Bool" + default: + parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) + if len(parts) != 4 { + return javaKind + } + switch parts[2] { + case "8", "16", "32", "64": + if parts[3] == "" { + return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) + } + return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) + + default: + return javaKind + } + } +} + +// alias returns an alias of the given string based on the aliasing rules +// or returns itself if no rule is matched. +func alias(aliases map[string]string, n string) string { + if alias, exist := aliases[n]; exist { + return alias + } + return n +} + +// methodNormalizer is a name transformer that modifies Solidity method names to +// conform to target language naming conventions. +var methodNormalizer = map[Lang]func(string) string{ + LangGo: abi.ToCamelCase, + LangJava: decapitalise, +} + +// capitalise makes a camel-case string which starts with an upper case character. +var capitalise = abi.ToCamelCase + +// decapitalise makes a camel-case string which starts with a lower case character. +func decapitalise(input string) string { + if len(input) == 0 { + return input + } + + goForm := abi.ToCamelCase(input) + return strings.ToLower(goForm[:1]) + goForm[1:] +} + +// structured checks whether a list of ABI data types has enough information to +// operate through a proper Go struct or if flat returns are needed. +func structured(args abi.Arguments) bool { + if len(args) < 2 { + return false + } + exists := make(map[string]bool) + for _, out := range args { + // If the name is anonymous, we can't organize into a struct + if out.Name == "" { + return false + } + // If the field name is empty when normalized or collides (var, Var, _var, _Var), + // we can't organize into a struct + field := capitalise(out.Name) + if field == "" || exists[field] { + return false + } + exists[field] = true + } + return true +} + +// hasStruct returns an indicator whether the given type is struct, struct slice +// or struct array. +func hasStruct(t abi.Type) bool { + switch t.T { + case abi.SliceTy: + return hasStruct(*t.Elem) + case abi.ArrayTy: + return hasStruct(*t.Elem) + case abi.TupleTy: + return true + default: + return false + } +} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go new file mode 100644 index 0000000..94f30cc --- /dev/null +++ b/accounts/abi/bind/template.go @@ -0,0 +1,703 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bind + +import "github.com/astranetworld/ast/accounts/abi" + +// tmplData is the data structure required to fill the binding template. +type tmplData struct { + Package string // Name of the package to place the generated file in + Contracts map[string]*tmplContract // List of contracts to generate into this file + Libraries map[string]string // Map the bytecode's link pattern to the library name + Structs map[string]*tmplStruct // Contract struct type definitions +} + +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { + Type string // Type name of the main contract binding + InputABI string // JSON ABI used as the input to generate the binding from + InputBin string // Optional EVM bytecode used to generate deploy code from + FuncSigs map[string]string // Optional map: string signature -> 4-byte signature + Constructor abi.Method // Contract constructor for deploy parametrization + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function + Events map[string]*tmplEvent // Contract events accessors + Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs + Library bool // Indicator whether the contract is a library +} + +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// and cached data fields. +type tmplMethod struct { + Original abi.Method // Original method as parsed by the abi package + Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) + Structured bool // Whether the returns should be accumulated into a struct +} + +// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed +// and cached data fields. +type tmplEvent struct { + Original abi.Event // Original event as parsed by the abi package + Normalized abi.Event // Normalized version of the parsed fields +} + +// tmplField is a wrapper around a struct field with binding language +// struct type definition and relative filed name. +type tmplField struct { + Type string // Field type representation depends on target binding language + Name string // Field name converted from the raw user-defined field name + SolKind abi.Type // Raw abi type information +} + +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated +// struct name. +type tmplStruct struct { + Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. + Fields []*tmplField // Struct fields definition depends on the binding language. +} + +// tmplSource is language to template mapping containing all the supported +// programming languages the package can generate to. +var tmplSource = map[Lang]string{ + LangGo: tmplSourceGo, + LangJava: tmplSourceJava, +} + +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. +const tmplSourceGo = ` +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package {{.Package}} + +import ( + "math/big" + "strings" + "errors" + +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +{{$structs := .Structs}} +{{range $structs}} + // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. + type {{.Name}} struct { + {{range $field := .Fields}} + {{$field.Name}} {{$field.Type}}{{end}} + } +{{end}} + +{{range $contract := .Contracts}} + // {{.Type}}MetaData contains all metadata data concerning the {{.Type}} contract. + var {{.Type}}MetaData = &bind.MetaData{ + ABI: "{{.InputABI}}", + {{if $contract.FuncSigs -}} + Sigs: map[string]string{ + {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", + {{end}} + }, + {{end -}} + {{if .InputBin -}} + Bin: "0x{{.InputBin}}", + {{end}} + } + // {{.Type}}ABI is the input ABI used to generate the binding from. + // Deprecated: Use {{.Type}}MetaData.ABI instead. + var {{.Type}}ABI = {{.Type}}MetaData.ABI + + {{if $contract.FuncSigs}} + // Deprecated: Use {{.Type}}MetaData.Sigs instead. + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs + {{end}} + + {{if .InputBin}} + // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. + // Deprecated: Use {{.Type}}MetaData.Bin instead. + var {{.Type}}Bin = {{.Type}}MetaData.Bin + + // Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + {{range $pattern, $name := .Libraries}} + {{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend) + {{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:]) + {{end}} + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + {{end}} + + // {{.Type}} is an auto generated Go binding around an Ethereum contract. + type {{.Type}} struct { + {{.Type}}Caller // Read-only binding to the contract + {{.Type}}Transactor // Write-only binding to the contract + {{.Type}}Filterer // Log filterer for contract events + } + + // {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract. + type {{.Type}}Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract. + type {{.Type}}Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events. + type {{.Type}}Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Session is an auto generated Go binding around an Ethereum contract, + // with pre-set call and transact options. + type {{.Type}}Session struct { + Contract *{{.Type}} // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract, + // with pre-set call options. + type {{.Type}}CallerSession struct { + Contract *{{.Type}}Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + } + + // {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract, + // with pre-set transact options. + type {{.Type}}TransactorSession struct { + Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract. + type {{.Type}}Raw struct { + Contract *{{.Type}} // Generic contract binding to access the raw methods on + } + + // {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. + type {{.Type}}CallerRaw struct { + Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on + } + + // {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. + type {{.Type}}TransactorRaw struct { + Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on + } + + // New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) { + contract, err := bind{{.Type}}(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + + // New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) { + contract, err := bind{{.Type}}(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Caller{contract: contract}, nil + } + + // New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) { + contract, err := bind{{.Type}}(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Transactor{contract: contract}, nil + } + + // New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) { + contract, err := bind{{.Type}}(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &{{.Type}}Filterer{contract: contract}, nil + } + + // bind{{.Type}} binds a generic wrapper to an already deployed contract. + func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...) + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...) + } + + {{range .Calls}} + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + {{end}} + + {{range .Events}} + // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { + Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration + } + // Next advances the iterator to the subsequent event, returning whether there + // are any more events found. In case of a retrieval or parsing error, false is + // returned and Error() can be queried for the exact failure. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool { + // If the iterator failed, stop iterating + if (it.fail != nil) { + return false + } + // If the iterator completed, deliver directly whatever's available + if (it.done) { + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } + } + // Error returns any retrieval or parsing error occurred during filtering. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error { + return it.fail + } + // Close terminates the iteration process, releasing any pending underlying + // resources. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error { + it.sub.Unsubscribe() + return nil + } + + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} + {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}} + Raw types.Log // Blockchain specific contextual infos + } + + // Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil + } + + // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil + } + + // Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil + } + + {{end}} +{{end}} +` + +// tmplSourceJava is the Java source template that the generated Java contract binding +// is based on. +const tmplSourceJava = ` +// This file is an automatically generated Java binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package {{.Package}}; + +import org.ethereum.geth.*; +import java.util.*; + +{{$structs := .Structs}} +{{range $contract := .Contracts}} +{{if not .Library}}public {{end}}class {{.Type}} { + // ABI is the input ABI used to generate the binding from. + public final static String ABI = "{{.InputABI}}"; + {{if $contract.FuncSigs}} + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + public final static Map {{.Type}}FuncSigs; + static { + Hashtable temp = new Hashtable(); + {{range $strsig, $binsig := .FuncSigs}}temp.put("{{$binsig}}", "{{$strsig}}"); + {{end}} + {{.Type}}FuncSigs = Collections.unmodifiableMap(temp); + } + {{end}} + {{if .InputBin}} + // BYTECODE is the compiled bytecode used for deploying new contracts. + public final static String BYTECODE = "0x{{.InputBin}}"; + + // deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}}); + String bytecode = BYTECODE; + {{if .Libraries}} + + // "link" contract to dependent libraries by deploying them first. + {{range $pattern, $name := .Libraries}} + {{capitalise $name}} {{decapitalise $name}}Inst = {{capitalise $name}}.deploy(auth, client); + bytecode = bytecode.replace("__${{$pattern}}$__", {{decapitalise $name}}Inst.Address.getHex().substring(2)); + {{end}} + {{end}} + {{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); + {{end}} + return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args)); + } + + // Internal constructor used by contract deployment. + private {{.Type}}(BoundContract deployment) { + this.Address = deployment.getAddress(); + this.Deployer = deployment.getDeployer(); + this.Contract = deployment; + } + {{end}} + + // Ethereum address where this contract is located at. + public final Address Address; + + // Ethereum transaction in which this contract was deployed (if known!). + public final Transaction Deployer; + + // Contract instance bound to a blockchain address. + private final BoundContract Contract; + + // Creates a new instance of {{.Type}}, bound to a specific deployed contract. + public {{.Type}}(Address address, EthereumClient client) throws Exception { + this(Geth.bindContract(address, ABI, client)); + } + + {{range .Calls}} + {{if gt (len .Normalized.Outputs) 1}} + // {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}. + public class {{capitalise .Normalized.Name}}Results { + {{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}}; + {{end}} + } + {{end}} + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); + {{end}} + + Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}}); + {{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}}); + {{end}} + + if (opts == null) { + opts = Geth.newCallOpts(); + } + this.Contract.call(opts, results, "{{.Original.Name}}", args); + {{if gt (len .Normalized.Outputs) 1}} + {{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results(); + {{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type $structs) .Type}}(); + {{end}} + return result; + {{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}} + {{end}} + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); + {{end}} + return this.Contract.transact(opts, "{{.Original.Name}}" , args); + } + {{end}} + + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { + return this.Contract.rawTransact(opts, calldata); + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + public Transaction Receive(TransactOpts opts) throws Exception { + return this.Contract.rawTransact(opts, null); + } + {{end}} +} +{{end}} +` diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go new file mode 100644 index 0000000..e5ac004 --- /dev/null +++ b/accounts/abi/bind/util.go @@ -0,0 +1,81 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bind + +import ( + "context" + "errors" + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "time" +) + +// NotFound is returned by API methods if the requested item does not exist. +var NotFound = errors.New("not found") + +// WaitMined waits for tx to be mined on the blockchain. +// It stops waiting when the context is canceled. +func WaitMined(ctx context.Context, b DeployBackend, tx *transaction.Transaction) (*block.Receipt, error) { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + + logger := log.New("hash", tx.Hash()) + for { + receipt, err := b.TransactionReceipt(ctx, tx.Hash()) + if err == nil { + return receipt, nil + } + + if errors.Is(err, NotFound) { + logger.Trace("Transaction not yet mined") + } else { + logger.Trace("Receipt retrieval failed", "err", err) + } + + // Wait for the next round. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-queryTicker.C: + } + } +} + +// WaitDeployed waits for a contract deployment transaction and returns the on-chain +// contract address when it is mined. It stops waiting when ctx is canceled. +func WaitDeployed(ctx context.Context, b DeployBackend, tx *transaction.Transaction) (types.Address, error) { + if tx.To() != nil { + return types.Address{}, errors.New("tx is not contract creation") + } + receipt, err := WaitMined(ctx, b, tx) + if err != nil { + return types.Address{}, err + } + if receipt.ContractAddress == (types.Address{}) { + return types.Address{}, errors.New("zero address") + } + // Check that code has indeed been deployed at the address. + // This matters on pre-Homestead chains: OOG in the constructor + // could leave an empty account behind. + code, err := b.CodeAt(ctx, receipt.ContractAddress, nil) + if err == nil && len(code) == 0 { + err = ErrNoCodeAfterDeploy + } + return receipt.ContractAddress, err +} diff --git a/accounts/abi/doc.go b/accounts/abi/doc.go new file mode 100644 index 0000000..0f9ebcd --- /dev/null +++ b/accounts/abi/doc.go @@ -0,0 +1,26 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package abi implements the Ethereum ABI (Application Binary +// Interface). +// +// The Ethereum ABI is strongly typed, known at compile time +// and static. This ABI will handle basic type casting; unsigned +// to signed and visa versa. It does not handle slice casting such +// as unsigned slice to signed slice. Bit size type casting is also +// handled. ints with a bit size of 32 will be properly cast to int256, +// etc. +package abi diff --git a/accounts/abi/error.go b/accounts/abi/error.go new file mode 100644 index 0000000..5d2b282 --- /dev/null +++ b/accounts/abi/error.go @@ -0,0 +1,92 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "bytes" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "strings" +) + +type Error struct { + Name string + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the error's signature used by the + // abi definition to identify event names and types. + ID types.Hash +} + +func NewError(name string, inputs Arguments) Error { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + typess := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + typess[i] = input.Type.String() + } + + str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", name, strings.Join(typess, ",")) + id := types.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Error{ + Name: name, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e *Error) String() string { + return e.str +} + +func (e *Error) Unpack(data []byte) (interface{}, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], e.ID[:4]) { + return "", errors.New("invalid data for unpacking") + } + return e.Inputs.Unpack(data[4:]) +} diff --git a/accounts/abi/error_handling.go b/accounts/abi/error_handling.go new file mode 100644 index 0000000..4e24181 --- /dev/null +++ b/accounts/abi/error_handling.go @@ -0,0 +1,81 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errBadBool = errors.New("abi: improperly encoded boolean value") +) + +// formatSliceString formats the reflection kind with the given slice size +// and returns a formatted string representation. +func formatSliceString(kind reflect.Kind, sliceSize int) string { + if sliceSize == -1 { + return fmt.Sprintf("[]%v", kind) + } + return fmt.Sprintf("[%d]%v", sliceSize, kind) +} + +// sliceTypeCheck checks that the given slice can by assigned to the reflection +// type in t. +func sliceTypeCheck(t Type, val reflect.Value) error { + if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) + } + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + } + + if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { + if val.Len() > 0 { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + } + + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) + } + return nil +} + +// typeCheck checks that the given reflection value can be assigned to the reflection +// type in t. +func typeCheck(t Type, value reflect.Value) error { + if t.T == SliceTy || t.T == ArrayTy { + return sliceTypeCheck(t, value) + } + + // Check base type validity. Element types will be checked later on. + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.GetType(), value.Type()) + } else { + return nil + } +} + +// typeErr returns a formatted type casting error. +func typeErr(expected, got interface{}) error { + return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +} diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 0000000..8d0932c --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,102 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "strings" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output. Anonymous events +// don't get the signature canonical representation as the first LOG topic. +type Event struct { + // Name is the event name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of event overloading. + // + // e.g. + // These are two events that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The event name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + + // RawName is the raw event name parsed from ABI. + RawName string + Anonymous bool + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. event foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the event's signature used by the + // abi definition to identify event names and types. + ID types.Hash +} + +// NewEvent creates a new Event. +// It sanitizes the input arguments to remove unnamed arguments. +// It also precomputes the id, signature and string representation +// of the event. +func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + typess := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + typess[i] = input.Type.String() + } + + str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(typess, ",")) + id := types.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Event{ + Name: name, + RawName: rawName, + Anonymous: anonymous, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e Event) String() string { + return e.str +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go new file mode 100644 index 0000000..a076125 --- /dev/null +++ b/accounts/abi/method.go @@ -0,0 +1,166 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "fmt" + "github.com/astranetworld/ast/common/crypto" + "strings" +) + +// FunctionType represents different types of functions a contract might have. +type FunctionType int + +const ( + // Constructor represents the constructor of the contract. + // The constructor function is called while deploying a contract. + Constructor FunctionType = iota + // Fallback represents the fallback function. + // This function is executed if no other function matches the given function + // signature and no receive function is specified. + Fallback + // Receive represents the receive function. + // This function is executed on plain Ether transfers. + Receive + // Function represents a normal function. + Function +) + +// Method represents a callable given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefore requires no Tx to be sent to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `false`. +// Input specifies the required input parameters for this gives method. +type Method struct { + // Name is the method name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of a function overload. + // + // e.g. + // These are two functions that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The method name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + RawName string // RawName is the raw method name parsed from ABI + + // Type indicates whether the method is a + // special fallback introduced in solidity v0.6.0 + Type FunctionType + + // StateMutability indicates the mutability state of method, + // the default value is nonpayable. It can be empty if the abi + // is generated by legacy compiler. + StateMutability string + + // Legacy indicators generated by compiler before v0.6.0 + Constant bool + Payable bool + + Inputs Arguments + Outputs Arguments + str string + // Sig returns the methods string signature according to the ABI spec. + // e.g. function foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + // ID returns the canonical representation of the method's signature used by the + // abi definition to identify method names and types. + ID []byte +} + +// NewMethod creates a new Method. +// A method should always be created using NewMethod. +// It also precomputes the sig representation and the string representation +// of the method. +func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method { + var ( + types = make([]string, len(inputs)) + inputNames = make([]string, len(inputs)) + outputNames = make([]string, len(outputs)) + ) + for i, input := range inputs { + inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name) + types[i] = input.Type.String() + } + for i, output := range outputs { + outputNames[i] = output.Type.String() + if len(output.Name) > 0 { + outputNames[i] += fmt.Sprintf(" %v", output.Name) + } + } + // calculate the signature and method id. Note only function + // has meaningful signature and id. + var ( + sig string + id []byte + ) + if funType == Function { + sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id = crypto.Keccak256([]byte(sig))[:4] + } + // Extract meaningful state mutability of solidity method. + // If it's default value, never print it. + state := mutability + if state == "nonpayable" { + state = "" + } + if state != "" { + state = state + " " + } + identity := fmt.Sprintf("function %v", rawName) + if funType == Fallback { + identity = "fallback" + } else if funType == Receive { + identity = "receive" + } else if funType == Constructor { + identity = "constructor" + } + str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", ")) + + return Method{ + Name: name, + RawName: rawName, + Type: funType, + StateMutability: mutability, + Constant: isConst, + Payable: isPayable, + Inputs: inputs, + Outputs: outputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (method Method) String() string { + return method.str +} + +// IsConstant returns the indicator whether the method is read-only. +func (method Method) IsConstant() bool { + return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant +} + +// IsPayable returns the indicator whether the method can process +// plain ether transfers. +func (method Method) IsPayable() bool { + return method.StateMutability == "payable" || method.Payable +} diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go new file mode 100644 index 0000000..4139bc4 --- /dev/null +++ b/accounts/abi/pack.go @@ -0,0 +1,85 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "github.com/astranetworld/ast/common" + "github.com/astranetworld/ast/common/math" + "github.com/astranetworld/ast/common/types" + "math/big" + "reflect" +) + +// packBytesSlice packs the given bytes as [L, V] as the canonical representation +// bytes slice. +func packBytesSlice(bytes []byte, l int) []byte { + len := packNum(reflect.ValueOf(l)) + return append(len, types.RightPadBytes(bytes, (l+31)/32*32)...) +} + +// packElement packs the given reflect value according to the abi specification in +// t. +func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { + switch t.T { + case IntTy, UintTy: + return packNum(reflectValue), nil + case StringTy: + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil + case AddressTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + + return types.LeftPadBytes(reflectValue.Bytes(), 32), nil + case BoolTy: + if reflectValue.Bool() { + return math.PaddedBigBytes(common.Big1, 32), nil + } + return math.PaddedBigBytes(common.Big0, 32), nil + case BytesTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + if reflectValue.Type() != reflect.TypeOf([]byte{}) { + return []byte{}, errors.New("Bytes type is neither slice nor array") + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil + case FixedBytesTy, FunctionTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + return types.RightPadBytes(reflectValue.Bytes(), 32), nil + default: + return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) + } +} + +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation. +func packNum(value reflect.Value) []byte { + switch kind := value.Kind(); kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return math.U256Bytes(new(big.Int).SetUint64(value.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return math.U256Bytes(big.NewInt(value.Int())) + case reflect.Ptr: + return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int))) + default: + panic("abi: fatal error") + } +} diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go new file mode 100644 index 0000000..7bbf882 --- /dev/null +++ b/accounts/abi/reflect.go @@ -0,0 +1,264 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "math/big" + "reflect" + "strings" +) + +// ConvertType converts an interface of a runtime type into a interface of the +// given type, e.g. turn this code: +// +// var fields []reflect.StructField +// +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// } +// +// into: +// +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() + } + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto +} + +// indirect recursively dereferences the value until it either gets the value +// or finds a big.Int +func indirect(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) { + return indirect(v.Elem()) + } + return v +} + +// reflectIntType returns the reflect using the given size and +// unsignedness. +func reflectIntType(unsigned bool, size int) reflect.Type { + if unsigned { + switch size { + case 8: + return reflect.TypeOf(uint8(0)) + case 16: + return reflect.TypeOf(uint16(0)) + case 32: + return reflect.TypeOf(uint32(0)) + case 64: + return reflect.TypeOf(uint64(0)) + } + } + switch size { + case 8: + return reflect.TypeOf(int8(0)) + case 16: + return reflect.TypeOf(int16(0)) + case 32: + return reflect.TypeOf(int32(0)) + case 64: + return reflect.TypeOf(int64(0)) + } + return reflect.TypeOf(&big.Int{}) +} + +// mustArrayToByteSlice creates a new byte slice with the exact same size as value +// and copies the bytes in value to the new slice. +func mustArrayToByteSlice(value reflect.Value) reflect.Value { + slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) + reflect.Copy(slice, value) + return slice +} + +// set attempts to assign src to dst by either setting, copying or otherwise. +// +// set is a bit more lenient when it comes to assignment and doesn't force an as +// strict ruleset as bare `reflect` does. +func set(dst, src reflect.Value) error { + dstType, srcType := dst.Type(), src.Type() + switch { + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()): + return set(dst.Elem(), src) + case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}): + return set(dst.Elem(), src) + case srcType.AssignableTo(dstType) && dst.CanSet(): + dst.Set(src) + case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet(): + return setSlice(dst, src) + case dstType.Kind() == reflect.Array: + return setArray(dst, src) + case dstType.Kind() == reflect.Struct: + return setStruct(dst, src) + default: + return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) + } + return nil +} + +// setSlice attempts to assign src to dst when slices are not assignable by default +// e.g. src: [][]byte -> dst: [][15]byte +// setSlice ignores if we cannot copy all of src' elements. +func setSlice(dst, src reflect.Value) error { + slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) + for i := 0; i < src.Len(); i++ { + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(slice) + return nil + } + return errors.New("Cannot set slice, destination not settable") +} + +func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) + } + array := reflect.New(dst.Type()).Elem() + min := src.Len() + if src.Len() > dst.Len() { + min = dst.Len() + } + for i := 0; i < min; i++ { + if err := set(array.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(array) + return nil + } + return errors.New("Cannot set array, destination not settable") +} + +func setStruct(dst, src reflect.Value) error { + for i := 0; i < src.NumField(); i++ { + srcField := src.Field(i) + dstField := dst.Field(i) + if !dstField.IsValid() || !srcField.IsValid() { + return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) + } + if err := set(dstField, srcField); err != nil { + return err + } + } + return nil +} + +// mapArgNamesToStructFields maps a slice of argument names to struct fields. +// +// first round: for each Exportable field that contains a `abi:""` tag and this field name +// exists in the given argument name list, pair them together. +// +// second round: for each argument name that has not been already linked, find what +// variable is expected to be mapped into, if it exists and has not been used, pair them. +// +// Note this function assumes the given value is a struct value. +func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { + typ := value.Type() + + abi2struct := make(map[string]string) + struct2abi := make(map[string]string) + + // first round ~~~ + for i := 0; i < typ.NumField(); i++ { + structFieldName := typ.Field(i).Name + + // skip private struct fields. + if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) { + continue + } + // skip fields that have no abi:"" tag. + tagName, ok := typ.Field(i).Tag.Lookup("abi") + if !ok { + continue + } + // check if tag is empty. + if tagName == "" { + return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName) + } + // check which argument field matches with the abi tag. + found := false + for _, arg := range argNames { + if arg == tagName { + if abi2struct[arg] != "" { + return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName) + } + // pair them + abi2struct[arg] = structFieldName + struct2abi[structFieldName] = arg + found = true + } + } + // check if this tag has been mapped. + if !found { + return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName) + } + } + + // second round ~~~ + for _, argName := range argNames { + structFieldName := ToCamelCase(argName) + + if structFieldName == "" { + return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct") + } + + // this abi has already been paired, skip it... unless there exists another, yet unassigned + // struct field with the same field name. If so, raise an error: + // abi: [ { "name": "value" } ] + // struct { Value *big.Int , Value1 *big.Int `abi:"value"`} + if abi2struct[argName] != "" { + if abi2struct[argName] != structFieldName && + struct2abi[structFieldName] == "" && + value.FieldByName(structFieldName).IsValid() { + return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName) + } + continue + } + + // return an error if this struct field has already been paired. + if struct2abi[structFieldName] != "" { + return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName) + } + + if value.FieldByName(structFieldName).IsValid() { + // pair them + abi2struct[argName] = structFieldName + struct2abi[structFieldName] = argName + } else { + // not paired, but annotate as used, to detect cases like + // abi : [ { "name": "value" }, { "name": "_value" } ] + // struct { Value *big.Int } + struct2abi[structFieldName] = argName + } + } + return abi2struct, nil +} diff --git a/accounts/abi/selector_parser.go b/accounts/abi/selector_parser.go new file mode 100644 index 0000000..63352f5 --- /dev/null +++ b/accounts/abi/selector_parser.go @@ -0,0 +1,176 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "fmt" +) + +type SelectorMarshaling struct { + Name string `json:"name"` + Type string `json:"type"` + Inputs []ArgumentMarshaling `json:"inputs"` +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isAlpha(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isIdentifierSymbol(c byte) bool { + return c == '$' || c == '_' +} + +func parseToken(unescapedSelector string, isIdent bool) (string, string, error) { + if len(unescapedSelector) == 0 { + return "", "", fmt.Errorf("empty token") + } + firstChar := unescapedSelector[0] + position := 1 + if !(isAlpha(firstChar) || (isIdent && isIdentifierSymbol(firstChar))) { + return "", "", fmt.Errorf("invalid token start: %c", firstChar) + } + for position < len(unescapedSelector) { + char := unescapedSelector[position] + if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char))) { + break + } + position++ + } + return unescapedSelector[:position], unescapedSelector[position:], nil +} + +func parseIdentifier(unescapedSelector string) (string, string, error) { + return parseToken(unescapedSelector, true) +} + +func parseElementaryType(unescapedSelector string) (string, string, error) { + parsedType, rest, err := parseToken(unescapedSelector, false) + if err != nil { + return "", "", fmt.Errorf("failed to parse elementary type: %v", err) + } + // handle arrays + for len(rest) > 0 && rest[0] == '[' { + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + for len(rest) > 0 && isDigit(rest[0]) { + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + } + if len(rest) == 0 || rest[0] != ']' { + return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", unescapedSelector[0]) + } + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + } + return parsedType, rest, nil +} + +func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) { + if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' { + return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0]) + } + parsedType, rest, err := parseType(unescapedSelector[1:]) + if err != nil { + return nil, "", fmt.Errorf("failed to parse type: %v", err) + } + result := []interface{}{parsedType} + for len(rest) > 0 && rest[0] != ')' { + parsedType, rest, err = parseType(rest[1:]) + if err != nil { + return nil, "", fmt.Errorf("failed to parse type: %v", err) + } + result = append(result, parsedType) + } + if len(rest) == 0 || rest[0] != ')' { + return nil, "", fmt.Errorf("expected ')', got '%s'", rest) + } + if len(rest) >= 3 && rest[1] == '[' && rest[2] == ']' { + return append(result, "[]"), rest[3:], nil + } + return result, rest[1:], nil +} + +func parseType(unescapedSelector string) (interface{}, string, error) { + if len(unescapedSelector) == 0 { + return nil, "", fmt.Errorf("empty type") + } + if unescapedSelector[0] == '(' { + return parseCompositeType(unescapedSelector) + } else { + return parseElementaryType(unescapedSelector) + } +} + +func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) { + arguments := make([]ArgumentMarshaling, 0) + for i, arg := range args { + // generate dummy name to avoid unmarshal issues + name := fmt.Sprintf("name%d", i) + if s, ok := arg.(string); ok { + arguments = append(arguments, ArgumentMarshaling{name, s, s, nil, false}) + } else if components, ok := arg.([]interface{}); ok { + subArgs, err := assembleArgs(components) + if err != nil { + return nil, fmt.Errorf("failed to assemble components: %v", err) + } + tupleType := "tuple" + if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" { + subArgs = subArgs[:len(subArgs)-1] + tupleType = "tuple[]" + } + arguments = append(arguments, ArgumentMarshaling{name, tupleType, tupleType, subArgs, false}) + } else { + return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg) + } + } + return arguments, nil +} + +// ParseSelector converts a method selector into a struct that can be JSON encoded +// and consumed by other functions in this package. +// Note, although uppercase letters are not part of the ABI spec, this function +// still accepts it as the general format is valid. +func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { + name, rest, err := parseIdentifier(unescapedSelector) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) + } + args := []interface{}{} + if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' { + rest = rest[2:] + } else { + args, rest, err = parseCompositeType(rest) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) + } + } + if len(rest) > 0 { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest) + } + + // Reassemble the fake ABI and construct the JSON + fakeArgs, err := assembleArgs(args) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector: %v", err) + } + + return SelectorMarshaling{name, "function", fakeArgs}, nil +} diff --git a/accounts/abi/topics.go b/accounts/abi/topics.go new file mode 100644 index 0000000..fbe08fc --- /dev/null +++ b/accounts/abi/topics.go @@ -0,0 +1,172 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "encoding/binary" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "math/big" + "reflect" +) + +// MakeTopics converts a filter query argument list into a filter topic set. +func MakeTopics(query ...[]interface{}) ([][]types.Hash, error) { + topics := make([][]types.Hash, len(query)) + for i, filter := range query { + for _, rule := range filter { + var topic types.Hash + + // Try to generate the topic based on simple types + switch rule := rule.(type) { + case types.Hash: + copy(topic[:], rule[:]) + case types.Address: + copy(topic[types.HashLength-types.AddressLength:], rule[:]) + case *big.Int: + blob := rule.Bytes() + copy(topic[types.HashLength-len(blob):], blob) + case bool: + if rule { + topic[types.HashLength-1] = 1 + } + case int8: + copy(topic[:], genIntType(int64(rule), 1)) + case int16: + copy(topic[:], genIntType(int64(rule), 2)) + case int32: + copy(topic[:], genIntType(int64(rule), 4)) + case int64: + copy(topic[:], genIntType(rule, 8)) + case uint8: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[types.HashLength-len(blob):], blob) + case uint16: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[types.HashLength-len(blob):], blob) + case uint32: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[types.HashLength-len(blob):], blob) + case uint64: + blob := new(big.Int).SetUint64(rule).Bytes() + copy(topic[types.HashLength-len(blob):], blob) + case string: + hash := crypto.Keccak256Hash([]byte(rule)) + copy(topic[:], hash[:]) + case []byte: + hash := crypto.Keccak256Hash(rule) + copy(topic[:], hash[:]) + + default: + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert stringS and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + + // Attempt to generate the topic from funky types + val := reflect.ValueOf(rule) + switch { + // static byte array + case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: + reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val) + default: + return nil, fmt.Errorf("unsupported indexed type: %T", rule) + } + } + topics[i] = append(topics[i], topic) + } + } + return topics, nil +} + +func genIntType(rule int64, size uint) []byte { + var topic [types.HashLength]byte + if rule < 0 { + // if a rule is negative, we need to put it into two's complement. + // extended to common.HashLength bytes. + topic = [types.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} + } + for i := uint(0); i < size; i++ { + topic[types.HashLength-i-1] = byte(rule >> (i * 8)) + } + return topic[:] +} + +// ParseTopics converts the indexed topic fields into actual log field values. +func ParseTopics(out interface{}, fields Arguments, topics []types.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name)) + field.Set(reflect.ValueOf(reconstr)) + }) +} + +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs. +func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []types.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + out[arg.Name] = reconstr + }) +} + +// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the +// provided set function. +// +// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 +// hashes as the topic value! +func parseTopicWithSetter(fields Arguments, topics []types.Hash, setter func(Argument, interface{})) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for i, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + var reconstr interface{} + switch arg.Type.T { + case TupleTy: + return errors.New("tuple type in topic reconstruction") + case StringTy, BytesTy, SliceTy, ArrayTy: + // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash + // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash + reconstr = topics[i] + case FunctionTy: + if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { + return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) + } + var tmp [24]byte + copy(tmp[:], topics[i][8:32]) + reconstr = tmp + default: + var err error + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) + if err != nil { + return err + } + } + // Use the setter function to store the value + setter(arg, reconstr) + } + + return nil +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go new file mode 100644 index 0000000..22e5af2 --- /dev/null +++ b/accounts/abi/type.go @@ -0,0 +1,425 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "github.com/astranetworld/ast/common/types" + "reflect" + "regexp" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// Type enumerator +const ( + IntTy byte = iota + UintTy + BoolTy + StringTy + SliceTy + ArrayTy + TupleTy + AddressTy + FixedBytesTy + BytesTy + HashTy + FixedPointTy + FunctionTy +) + +// Type is the reflection of the supported argument type. +type Type struct { + Elem *Type + Size int + T byte // Our own type checking + + stringKind string // holds the unparsed string for deriving signatures + + // Tuple relative fields + TupleRawName string // Raw struct name defined in source code, may be empty. + TupleElems []*Type // Type information of all tuple fields + TupleRawNames []string // Raw field name of all tuple fields + TupleType reflect.Type // Underlying struct of the tuple +} + +var ( + // typeRegex parses the abi sub types + typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") +) + +// NewType creates a new reflection type of abi type given in t. +func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) { + // check that array brackets are equal if they exist + if strings.Count(t, "[") != strings.Count(t, "]") { + return Type{}, fmt.Errorf("invalid arg type in abi") + } + typ.stringKind = t + + // if there are brackets, get ready to go into slice/array mode and + // recursively create the type + if strings.Count(t, "[") != 0 { + // Note internalType can be empty here. + subInternal := internalType + if i := strings.LastIndex(internalType, "["); i != -1 { + subInternal = subInternal[:i] + } + // recursively embed the type + i := strings.LastIndex(t, "[") + embeddedType, err := NewType(t[:i], subInternal, components) + if err != nil { + return Type{}, err + } + // grab the last cell and create a type from there + sliced := t[i:] + // grab the slice size with regexp + re := regexp.MustCompile("[0-9]+") + intz := re.FindAllString(sliced, -1) + + if len(intz) == 0 { + // is a slice + typ.T = SliceTy + typ.Elem = &embeddedType + typ.stringKind = embeddedType.stringKind + sliced + } else if len(intz) == 1 { + // is an array + typ.T = ArrayTy + typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + typ.stringKind = embeddedType.stringKind + sliced + } else { + return Type{}, fmt.Errorf("invalid formatting of array type") + } + return typ, err + } + // parse the type and size of the abi-type. + matches := typeRegex.FindAllStringSubmatch(t, -1) + if len(matches) == 0 { + return Type{}, fmt.Errorf("invalid type '%v'", t) + } + parsedType := matches[0] + + // varSize is the size of the variable + var varSize int + if len(parsedType[3]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + } else { + if parsedType[0] == "uint" || parsedType[0] == "int" { + // this should fail because it means that there's something wrong with + // the abi type (the compiler should always format it to the size...always) + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + } + // varType is the parsed abi type + switch varType := parsedType[1]; varType { + case "int": + typ.Size = varSize + typ.T = IntTy + case "uint": + typ.Size = varSize + typ.T = UintTy + case "bool": + typ.T = BoolTy + case "address": + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.T = StringTy + case "bytes": + if varSize == 0 { + typ.T = BytesTy + } else { + if varSize > 32 { + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + typ.T = FixedBytesTy + typ.Size = varSize + } + case "tuple": + var ( + fields []reflect.StructField + elems []*Type + names []string + expression string // canonical parameter expression + used = make(map[string]bool) + ) + expression += "(" + for idx, c := range components { + cType, err := NewType(c.Type, c.InternalType, c.Components) + if err != nil { + return Type{}, err + } + name := ToCamelCase(c.Name) + if name == "" { + return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + } + fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] }) + if err != nil { + return Type{}, err + } + used[fieldName] = true + if !isValidFieldName(fieldName) { + return Type{}, fmt.Errorf("field %d has invalid name", idx) + } + fields = append(fields, reflect.StructField{ + Name: fieldName, // reflect.StructOf will panic for any exported field. + Type: cType.GetType(), + Tag: reflect.StructTag("json:\"" + c.Name + "\""), + }) + elems = append(elems, &cType) + names = append(names, c.Name) + expression += cType.stringKind + if idx != len(components)-1 { + expression += "," + } + } + expression += ")" + + typ.TupleType = reflect.StructOf(fields) + typ.TupleElems = elems + typ.TupleRawNames = names + typ.T = TupleTy + typ.stringKind = expression + + const structPrefix = "struct " + // After solidity 0.5.10, a new field of abi "internalType" + // is introduced. From that we can obtain the struct name + // user defined in the source code. + if internalType != "" && strings.HasPrefix(internalType, structPrefix) { + // Foo.Bar type definition is not allowed in golang, + // convert the format to FooBar + typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "") + } + + case "function": + typ.T = FunctionTy + typ.Size = 24 + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + + return +} + +// GetType returns the reflection type of the ABI type. +func (t Type) GetType() reflect.Type { + switch t.T { + case IntTy: + return reflectIntType(false, t.Size) + case UintTy: + return reflectIntType(true, t.Size) + case BoolTy: + return reflect.TypeOf(false) + case StringTy: + return reflect.TypeOf("") + case SliceTy: + return reflect.SliceOf(t.Elem.GetType()) + case ArrayTy: + return reflect.ArrayOf(t.Size, t.Elem.GetType()) + case TupleTy: + return t.TupleType + case AddressTy: + return reflect.TypeOf(types.Address{}) + case FixedBytesTy: + return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0))) + case BytesTy: + return reflect.SliceOf(reflect.TypeOf(byte(0))) + case HashTy: + // hashtype currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FixedPointTy: + // fixedpoint type currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FunctionTy: + return reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + panic("Invalid type") + } +} + +// String implements Stringer. +func (t Type) String() (out string) { + return t.stringKind +} + +func (t Type) pack(v reflect.Value) ([]byte, error) { + // dereference pointer first if it's a pointer + v = indirect(v) + if err := typeCheck(t, v); err != nil { + return nil, err + } + + switch t.T { + case SliceTy, ArrayTy: + var ret []byte + + if t.requiresLengthPrefix() { + // append length + ret = append(ret, packNum(reflect.ValueOf(v.Len()))...) + } + + // calculate offset if any + offset := 0 + offsetReq := isDynamicType(*t.Elem) + if offsetReq { + offset = getTypeSize(*t.Elem) * v.Len() + } + var tail []byte + for i := 0; i < v.Len(); i++ { + val, err := t.Elem.pack(v.Index(i)) + if err != nil { + return nil, err + } + if !offsetReq { + ret = append(ret, val...) + continue + } + ret = append(ret, packNum(reflect.ValueOf(offset))...) + offset += len(val) + tail = append(tail, val...) + } + return append(ret, tail...), nil + case TupleTy: + // (T1,...,Tk) for k >= 0 and any types T1, …, Tk + // enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k)) + // where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static + // type as + // head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string) + // and as + // head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)))) + // tail(X(i)) = enc(X(i)) + // otherwise, i.e. if Ti is a dynamic type. + fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v) + if err != nil { + return nil, err + } + // Calculate prefix occupied size. + offset := 0 + for _, elem := range t.TupleElems { + offset += getTypeSize(*elem) + } + var ret, tail []byte + for i, elem := range t.TupleElems { + field := v.FieldByName(fieldmap[t.TupleRawNames[i]]) + if !field.IsValid() { + return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i]) + } + val, err := elem.pack(field) + if err != nil { + return nil, err + } + if isDynamicType(*elem) { + ret = append(ret, packNum(reflect.ValueOf(offset))...) + tail = append(tail, val...) + offset += len(val) + } else { + ret = append(ret, val...) + } + } + return append(ret, tail...), nil + + default: + return packElement(t, v) + } +} + +// requireLengthPrefix returns whether the type requires any sort of length +// prefixing. +func (t Type) requiresLengthPrefix() bool { + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy +} + +// isDynamicType returns true if the type is dynamic. +// The following types are called “dynamic”: +// * bytes +// * string +// * T[] for any T +// * T[k] for any dynamic T and any k >= 0 +// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k +func isDynamicType(t Type) bool { + if t.T == TupleTy { + for _, elem := range t.TupleElems { + if isDynamicType(*elem) { + return true + } + } + return false + } + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem)) +} + +// getTypeSize returns the size that this type needs to occupy. +// We distinguish static and dynamic types. Static types are encoded in-place +// and dynamic types are encoded at a separately allocated location after the +// current block. +// So for a static variable, the size returned represents the size that the +// variable actually occupies. +// For a dynamic variable, the returned size is fixed 32 bytes, which is used +// to store the location reference for actual value storage. +func getTypeSize(t Type) int { + if t.T == ArrayTy && !isDynamicType(*t.Elem) { + // Recursively calculate type size if it is a nested array + if t.Elem.T == ArrayTy || t.Elem.T == TupleTy { + return t.Size * getTypeSize(*t.Elem) + } + return t.Size * 32 + } else if t.T == TupleTy && !isDynamicType(t) { + total := 0 + for _, elem := range t.TupleElems { + total += getTypeSize(*elem) + } + return total + } + return 32 +} + +// isLetter reports whether a given 'rune' is classified as a Letter. +// This method is copied from reflect/type.go +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +// isValidFieldName checks if a string is a valid (struct) field name or not. +// +// According to the language spec, a field name should be an identifier. +// +// identifier = letter { letter | unicode_digit } . +// letter = unicode_letter | "_" . +// This method is copied from reflect/type.go +func isValidFieldName(fieldName string) bool { + for i, c := range fieldName { + if i == 0 && !isLetter(c) { + return false + } + + if !(isLetter(c) || unicode.IsDigit(c)) { + return false + } + } + + return len(fieldName) > 0 +} diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go new file mode 100644 index 0000000..f4ef8dd --- /dev/null +++ b/accounts/abi/unpack.go @@ -0,0 +1,297 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import ( + "encoding/binary" + "fmt" + "github.com/astranetworld/ast/common" + "github.com/astranetworld/ast/common/types" + "math/big" + "reflect" +) + +var ( + // MaxUint256 is the maximum value that can be represented by a uint256. + MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) + // MaxInt256 is the maximum value that can be represented by a int256. + MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) +) + +// ReadInteger reads the integer based on its kind and returns the appropriate value. +func ReadInteger(typ Type, b []byte) interface{} { + if typ.T == UintTy { + switch typ.Size { + case 8: + return b[len(b)-1] + case 16: + return binary.BigEndian.Uint16(b[len(b)-2:]) + case 32: + return binary.BigEndian.Uint32(b[len(b)-4:]) + case 64: + return binary.BigEndian.Uint64(b[len(b)-8:]) + default: + // the only case left for unsigned integer is uint256. + return new(big.Int).SetBytes(b) + } + } + switch typ.Size { + case 8: + return int8(b[len(b)-1]) + case 16: + return int16(binary.BigEndian.Uint16(b[len(b)-2:])) + case 32: + return int32(binary.BigEndian.Uint32(b[len(b)-4:])) + case 64: + return int64(binary.BigEndian.Uint64(b[len(b)-8:])) + default: + // the only case left for integer is int256 + // big.SetBytes can't tell if a number is negative or positive in itself. + // On EVM, if the returned number > max int256, it is negative. + // A number is > max int256 if the bit at position 255 is set. + ret := new(big.Int).SetBytes(b) + if ret.Bit(255) == 1 { + ret.Add(MaxUint256, new(big.Int).Neg(ret)) + ret.Add(ret, common.Big1) + ret.Neg(ret) + } + return ret + } +} + +// readBool reads a bool. +func readBool(word []byte) (bool, error) { + for _, b := range word[:31] { + if b != 0 { + return false, errBadBool + } + } + switch word[31] { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, errBadBool + } +} + +// A function type is simply the address with the function selection signature at the end. +// +// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { + if t.T != FunctionTy { + return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") + } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { + err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) + } else { + copy(funcTy[:], word[0:24]) + } + return +} + +// ReadFixedBytes uses reflection to create a fixed array to be read from. +func ReadFixedBytes(t Type, word []byte) (interface{}, error) { + if t.T != FixedBytesTy { + return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") + } + // convert + array := reflect.New(t.GetType()).Elem() + + reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil +} + +// forEachUnpack iteratively unpack elements. +func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if size < 0 { + return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) + } + if start+32*size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) + } + + // this value will become our slice or our array, depending on the type + var refSlice reflect.Value + + if t.T == SliceTy { + // declare our slice + refSlice = reflect.MakeSlice(t.GetType(), size, size) + } else if t.T == ArrayTy { + // declare our array + refSlice = reflect.New(t.GetType()).Elem() + } else { + return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") + } + + // Arrays have packed elements, resulting in longer unpack steps. + // Slices have just 32 bytes per element (pointing to the contents). + elemSize := getTypeSize(*t.Elem) + + for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { + inter, err := toGoType(i, *t.Elem, output) + if err != nil { + return nil, err + } + + // append the item to our reflect slice + refSlice.Index(j).Set(reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + +func forTupleUnpack(t Type, output []byte) (interface{}, error) { + retval := reflect.New(t.GetType()).Elem() + virtualArgs := 0 + for index, elem := range t.TupleElems { + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + if err != nil { + return nil, err + } + if elem.T == ArrayTy && !isDynamicType(*elem) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(*elem)/32 - 1 + } else if elem.T == TupleTy && !isDynamicType(*elem) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(*elem)/32 - 1 + } + retval.Field(index).Set(reflect.ValueOf(marshalledValue)) + } + return retval.Interface(), nil +} + +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. +func toGoType(index int, t Type, output []byte) (interface{}, error) { + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) + } + + var ( + returnOutput []byte + begin, length int + err error + ) + + // if we require a length prefix, find the beginning word and size returned. + if t.requiresLengthPrefix() { + begin, length, err = lengthPrefixPointsTo(index, output) + if err != nil { + return nil, err + } + } else { + returnOutput = output[index : index+32] + } + + switch t.T { + case TupleTy: + if isDynamicType(t) { + begin, err := tuplePointsTo(index, output) + if err != nil { + return nil, err + } + return forTupleUnpack(t, output[begin:]) + } + return forTupleUnpack(t, output[index:]) + case SliceTy: + return forEachUnpack(t, output[begin:], 0, length) + case ArrayTy: + if isDynamicType(*t.Elem) { + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(output)) { + return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) + } + return forEachUnpack(t, output[offset:], 0, t.Size) + } + return forEachUnpack(t, output[index:], 0, t.Size) + case StringTy: // variable arrays are written at the end of the return bytes + return string(output[begin : begin+length]), nil + case IntTy, UintTy: + return ReadInteger(t, returnOutput), nil + case BoolTy: + return readBool(returnOutput) + case AddressTy: + return types.BytesToAddress(returnOutput), nil + case HashTy: + return types.BytesToHash(returnOutput), nil + case BytesTy: + return output[begin : begin+length], nil + case FixedBytesTy: + return ReadFixedBytes(t, returnOutput) + case FunctionTy: + return readFunctionType(t, returnOutput) + default: + return nil, fmt.Errorf("abi: unknown type %v", t.T) + } +} + +// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type. +func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { + bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32]) + bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) + + if bigOffsetEnd.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength) + } + + if bigOffsetEnd.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd) + } + + offsetEnd := int(bigOffsetEnd.Uint64()) + lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd]) + + totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig) + if totalSize.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize) + } + + if totalSize.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) + } + start = int(bigOffsetEnd.Uint64()) + length = int(lengthBig.Uint64()) + return +} + +// tuplePointsTo resolves the location reference for dynamic tuple. +func tuplePointsTo(index int, output []byte) (start int, err error) { + offset := new(big.Int).SetBytes(output[index : index+32]) + outputLen := big.NewInt(int64(len(output))) + + if offset.Cmp(outputLen) > 0 { + return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) + } + if offset.BitLen() > 63 { + return 0, fmt.Errorf("abi offset larger than int64: %v", offset) + } + return int(offset.Uint64()), nil +} diff --git a/accounts/abi/utils.go b/accounts/abi/utils.go new file mode 100644 index 0000000..6fdef8f --- /dev/null +++ b/accounts/abi/utils.go @@ -0,0 +1,40 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package abi + +import "fmt" + +// ResolveNameConflict returns the next available name for a given thing. +// This helper can be used for lots of purposes: +// +// - In solidity function overloading is supported, this function can fix +// the name conflicts of overloaded functions. +// - In golang binding generation, the parameter(in function, event, error, +// and struct definition) name will be converted to camelcase style which +// may eventually lead to name conflicts. +// +// Name conflicts are mostly resolved by adding number suffix. e.g. if the abi contains +// Methods "send" and "send1", ResolveNameConflict would return "send2" for input "send". +func ResolveNameConflict(rawName string, used func(string) bool) string { + name := rawName + ok := used(name) + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + ok = used(name) + } + return name +} diff --git a/accounts/accounts.go b/accounts/accounts.go new file mode 100644 index 0000000..98bc9bc --- /dev/null +++ b/accounts/accounts.go @@ -0,0 +1,226 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package accounts implements high level Ethereum account management. +package accounts + +import ( + "fmt" + "math/big" + + "github.com/astranetworld/ast/common" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + event "github.com/astranetworld/ast/modules/event/v2" + "golang.org/x/crypto/sha3" +) + +// Account represents an Ethereum account located at a specific location defined +// by the optional URL field. +type Account struct { + Address types.Address `json:"address"` // Ethereum account address derived from the key + URL URL `json:"url"` // Optional resource locator within a backend +} + +const ( + MimetypeDataWithValidator = "data/validator" + MimetypeTypedData = "data/typed" + MimetypeClique = "application/x-clique-header" + MimetypeTextPlain = "text/plain" +) + +// Wallet represents a software or hardware wallet that might contain one or more +// accounts (derived from the same seed). +type Wallet interface { + // URL retrieves the canonical path under which this wallet is reachable. It is + // used by upper layers to define a sorting order over all wallets from multiple + // backends. + URL() URL + + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. It is not meant to unlock or + // decrypt account keys, rather simply to establish a connection to hardware + // wallets and/or to access derivation seeds. + // + // The passphrase parameter may or may not be used by the implementation of a + // particular wallet instance. The reason there is no passwordless open method + // is to strive towards a uniform wallet handling, oblivious to the different + // backend providers. + // + // Please note, if you open a wallet, you must close it to release any allocated + // resources (especially important when working with hardware wallets). + Open(passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Accounts retrieves the list of signing accounts the wallet is currently aware + // of. For hierarchical deterministic wallets, the list will not be exhaustive, + // rather only contain the accounts explicitly pinned during account derivation. + Accounts() []Account + + // Contains returns whether an account is part of this particular wallet or not. + Contains(account Account) bool + + // Derive attempts to explicitly derive a hierarchical deterministic account at + // the specified derivation path. If requested, the derived account will be added + // to the wallet's tracked account list. + Derive(path DerivationPath, pin bool) (Account, error) + + // SelfDerive sets a base account derivation path from which the wallet attempts + // to discover non zero accounts and automatically add them to list of tracked + // accounts. + // + // Note, self derivation will increment the last component of the specified path + // opposed to descending into a child path to allow discovering accounts starting + // from non zero components. + // + // Some hardware wallets switched derivation paths through their evolution, so + // this method supports providing multiple bases to discover old user accounts + // too. Only the last base will be used to derive the next empty account. + // + // You can disable automatic account discovery by calling SelfDerive with a nil + // chain state reader. + SelfDerive(bases []DerivationPath, chain common.ChainStateReader) + + // SignData requests the wallet to sign the hash of the given data + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignDataWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignData(account Account, mimeType string, data []byte) ([]byte, error) + + // SignDataWithPassphrase is identical to SignData, but also takes a password + // NOTE: there's a chance that an erroneous call might mistake the two strings, and + // supply password in the mimetype field, or vice versa. Thus, an implementation + // should never echo the mimetype or return the mimetype in the error-response + SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) + + // SignText requests the wallet to sign the hash of a given piece of data, prefixed + // by the Ethereum prefix scheme + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTextWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + // + // This method should return the signature in 'canonical' format, with v 0 or 1. + SignText(account Account, text []byte) ([]byte, error) + + // SignTextWithPassphrase is identical to Signtext, but also takes a password + SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) + + // SignTx requests the wallet to sign the given transaction. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignTx(account Account, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) + + // SignTxWithPassphrase is identical to SignTx, but also takes a password + SignTxWithPassphrase(account Account, passphrase string, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) +} + +// Backend is a "wallet provider" that may contain a batch of accounts they can +// sign transactions with and upon request, do so. +type Backend interface { + // Wallets retrieves the list of wallets the backend is currently aware of. + // + // The returned wallets are not opened by default. For software HD wallets this + // means that no base seeds are decrypted, and for hardware wallets that no actual + // connection is established. + // + // The resulting wallet list will be sorted alphabetically based on its internal + // URL assigned by the backend. Since wallets (especially hardware) may come and + // go, the same wallet might appear at a different positions in the list during + // subsequent retrievals. + Wallets() []Wallet + + // Subscribe creates an async subscription to receive notifications when the + // backend detects the arrival or departure of a wallet. + Subscribe(sink chan<- WalletEvent) event.Subscription +} + +// TextHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextHash(data []byte) []byte { + hash, _ := TextAndHash(data) + return hash +} + +// TextAndHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextAndHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg +} + +// WalletEventType represents the different event types that can be fired by +// the wallet subscription subsystem. +type WalletEventType int + +const ( + // WalletArrived is fired when a new wallet is detected either via USB or via + // a filesystem event in the keystore. + WalletArrived WalletEventType = iota + + // WalletOpened is fired when a wallet is successfully opened with the purpose + // of starting any background processes such as automatic key derivation. + WalletOpened + + // WalletDropped + WalletDropped +) + +// WalletEvent is an event fired by an account backend when a wallet arrival or +// departure is detected. +type WalletEvent struct { + Wallet Wallet // Wallet instance arrived or departed + Kind WalletEventType // Event type that happened in the system +} diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go new file mode 100644 index 0000000..55ff70b --- /dev/null +++ b/accounts/accounts_test.go @@ -0,0 +1,56 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "bytes" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/math" + "github.com/ledgerwatch/secp256k1" + "testing" + + "github.com/astranetworld/ast/common/hexutil" +) + +func TestTextHash(t *testing.T) { + hash := TextHash([]byte("Hello Joe")) + want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b") + if !bytes.Equal(hash, want) { + t.Fatalf("wrong hash: %x", hash) + } +} + +func TestSign(t *testing.T) { + private, err := crypto.HexToECDSA("DEBF9EAE7820E23201EEE9D51413B6D2CDF06C320D7152C2D3BC1FB6C42DA23D") + if nil != err { + t.Error(err) + } + seckey := math.PaddedBigBytes(private.D, private.Params().BitSize/8) + defer zeroBytes(seckey) + + msg, _ := hexutil.Decode("0x08712134afd46d42a45a5ed0e9311933138a06041b88062e590a613c2673c29f") + signature, err := secp256k1.Sign(msg, seckey) + + t.Logf("%v", err) + t.Logf("%s", hexutil.Encode(signature)) +} + +func zeroBytes(bytes []byte) { + for i := range bytes { + bytes[i] = 0 + } +} diff --git a/accounts/errors.go b/accounts/errors.go new file mode 100644 index 0000000..fab58ed --- /dev/null +++ b/accounts/errors.go @@ -0,0 +1,67 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "errors" + "fmt" +) + +// ErrUnknownAccount is returned for any requested operation for which no backend +// provides the specified account. +var ErrUnknownAccount = errors.New("unknown account") + +// ErrUnknownWallet is returned for any requested operation for which no backend +// provides the specified wallet. +var ErrUnknownWallet = errors.New("unknown wallet") + +// ErrNotSupported is returned when an operation is requested from an account +// backend that it does not support. +var ErrNotSupported = errors.New("not supported") + +// ErrInvalidPassphrase is returned when a decryption operation receives a bad +// passphrase. +var ErrInvalidPassphrase = errors.New("invalid password") + +// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the +// second time. +var ErrWalletAlreadyOpen = errors.New("wallet already open") + +// ErrWalletClosed is returned if a wallet is offline. +var ErrWalletClosed = errors.New("wallet closed") + +// AuthNeededError is returned by backends for signing requests where the user +// is required to provide further authentication before signing can succeed. +// +// This usually means either that a password needs to be supplied, or perhaps a +// one time PIN code displayed by some hardware device. +type AuthNeededError struct { + Needed string // Extra authentication the user needs to provide +} + +// NewAuthNeededError creates a new authentication error with the extra details +// about the needed fields set. +func NewAuthNeededError(needed string) error { + return &AuthNeededError{ + Needed: needed, + } +} + +// Error implements the standard error interface. +func (err *AuthNeededError) Error() string { + return fmt.Sprintf("authentication needed: %s", err.Needed) +} diff --git a/accounts/external/backend.go b/accounts/external/backend.go new file mode 100644 index 0000000..4d7fbb1 --- /dev/null +++ b/accounts/external/backend.go @@ -0,0 +1,269 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package external + +//import ( +// "fmt" +// "math/big" +// "sync" +// +// "github.com/astranetworld/ast/accounts" +// "github.com/astranetworld/ast/common" +// types2 "github.com/astranetworld/ast/common/types" +// "github.com/astranetworld/ast/internal/avm/common/hexutil" +// "github.com/astranetworld/ast/internal/avm/types" +// "github.com/astranetworld/ast/log" +// event "github.com/astranetworld/ast/modules/event/v2" +// "github.com/astranetworld/ast/rpc" +// "github.com/astranetworld/ast/signer/core/apitypes" +//) +// +//type ExternalBackend struct { +// signers []accounts.Wallet +//} +// +//func (eb *ExternalBackend) Wallets() []accounts.Wallet { +// return eb.signers +//} +// +//func NewExternalBackend(endpoint string) (*ExternalBackend, error) { +// signer, err := NewExternalSigner(endpoint) +// if err != nil { +// return nil, err +// } +// return &ExternalBackend{ +// signers: []accounts.Wallet{signer}, +// }, nil +//} +// +//func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { +// return event.NewSubscription(func(quit <-chan struct{}) error { +// <-quit +// return nil +// }) +//} +// +//// ExternalSigner provides an API to interact with an external signer (clef) +//// It proxies request to the external signer while forwarding relevant +//// request headers +//type ExternalSigner struct { +// client *rpc.Client +// endpoint string +// status string +// cacheMu sync.RWMutex +// cache []accounts.Account +//} +// +//func NewExternalSigner(endpoint string) (*ExternalSigner, error) { +// client, err := rpc.Dial(endpoint) +// if err != nil { +// return nil, err +// } +// extsigner := &ExternalSigner{ +// client: client, +// endpoint: endpoint, +// } +// // Check if reachable +// version, err := extsigner.pingVersion() +// if err != nil { +// return nil, err +// } +// extsigner.status = fmt.Sprintf("ok [version=%v]", version) +// return extsigner, nil +//} +// +//func (api *ExternalSigner) URL() accounts.URL { +// return accounts.URL{ +// Scheme: "extapi", +// Path: api.endpoint, +// } +//} +// +//func (api *ExternalSigner) Status() (string, error) { +// return api.status, nil +//} +// +//func (api *ExternalSigner) Open(passphrase string) error { +// return fmt.Errorf("operation not supported on external signers") +//} +// +//func (api *ExternalSigner) Close() error { +// return fmt.Errorf("operation not supported on external signers") +//} +// +//func (api *ExternalSigner) Accounts() []accounts.Account { +// var accnts []accounts.Account +// res, err := api.listAccounts() +// if err != nil { +// log.Error("account listing failed", "error", err) +// return accnts +// } +// for _, addr := range res { +// accnts = append(accnts, accounts.Account{ +// URL: accounts.URL{ +// Scheme: "extapi", +// Path: api.endpoint, +// }, +// Address: addr, +// }) +// } +// api.cacheMu.Lock() +// api.cache = accnts +// api.cacheMu.Unlock() +// return accnts +//} +// +//func (api *ExternalSigner) Contains(account accounts.Account) bool { +// api.cacheMu.RLock() +// defer api.cacheMu.RUnlock() +// if api.cache == nil { +// // If we haven't already fetched the accounts, it's time to do so now +// api.cacheMu.RUnlock() +// api.Accounts() +// api.cacheMu.RLock() +// } +// for _, a := range api.cache { +// if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { +// return true +// } +// } +// return false +//} +// +//func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { +// return accounts.Account{}, fmt.Errorf("operation not supported on external signers") +//} +// +//func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain common.ChainStateReader) { +// log.Error("operation SelfDerive not supported on external signers") +//} +// +//// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +//func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { +// var res hexutil.Bytes +// var signAddress = common.NewMixedcaseAddress(account.Address) +// if err := api.client.Call(&res, "account_signData", +// mimeType, +// &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined +// hexutil.Encode(data)); err != nil { +// return nil, err +// } +// // If V is on 27/28-form, convert to 0/1 for Clique +// if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { +// res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use +// } +// return res, nil +//} +// +//func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { +// var signature hexutil.Bytes +// var signAddress = common.NewMixedcaseAddress(account.Address) +// if err := api.client.Call(&signature, "account_signData", +// accounts.MimetypeTextPlain, +// &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined +// hexutil.Encode(text)); err != nil { +// return nil, err +// } +// if signature[64] == 27 || signature[64] == 28 { +// // If clef is used as a backend, it may already have transformed +// // the signature to ethereum-type signature. +// signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 +// } +// return signature, nil +//} +// +//// signTransactionResult represents the signinig result returned by clef. +//type signTransactionResult struct { +// Raw hexutil.Bytes `json:"raw"` +// Tx *types.Transaction `json:"tx"` +//} +// +//// SignTx sends the transaction to the external signer. +//// If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned +//// by the external signer. For non-legacy transactions, the chain ID of the +//// transaction overrides the chainID parameter. +//func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +// data := hexutil.Bytes(tx.Data()) +// var to *common.MixedcaseAddress +// if tx.To() != nil { +// t := common.NewMixedcaseAddress(*tx.To()) +// to = &t +// } +// args := &apitypes.SendTxArgs{ +// Data: &data, +// Nonce: hexutil.Uint64(tx.Nonce()), +// Value: hexutil.Big(*tx.Value()), +// Gas: hexutil.Uint64(tx.Gas()), +// To: to, +// From: common.NewMixedcaseAddress(account.Address), +// } +// switch tx.Type() { +// case types.LegacyTxType, types.AccessListTxType: +// args.GasPrice = (*hexutil.Big)(tx.GasPrice()) +// case types.DynamicFeeTxType: +// args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) +// args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) +// default: +// return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) +// } +// // We should request the default chain id that we're operating with +// // (the chain we're executing on) +// if chainID != nil && chainID.Sign() != 0 { +// args.ChainID = (*hexutil.Big)(chainID) +// } +// if tx.Type() != types.LegacyTxType { +// // However, if the user asked for a particular chain id, then we should +// // use that instead. +// if tx.ChainId().Sign() != 0 { +// args.ChainID = (*hexutil.Big)(tx.ChainId()) +// } +// accessList := tx.AccessList() +// args.AccessList = &accessList +// } +// var res signTransactionResult +// if err := api.client.Call(&res, "account_signTransaction", args); err != nil { +// return nil, err +// } +// return res.Tx, nil +//} +// +//func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { +// return []byte{}, fmt.Errorf("password-operations not supported on external signers") +//} +// +//func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +// return nil, fmt.Errorf("password-operations not supported on external signers") +//} +//func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { +// return nil, fmt.Errorf("password-operations not supported on external signers") +//} +// +//func (api *ExternalSigner) listAccounts() ([]types2.Address, error) { +// var res []types2.Address +// if err := api.client.Call(&res, "account_list"); err != nil { +// return nil, err +// } +// return res, nil +//} +// +//func (api *ExternalSigner) pingVersion() (string, error) { +// var v string +// if err := api.client.Call(&v, "account_version"); err != nil { +// return "", err +// } +// return v, nil +//} diff --git a/accounts/hd.go b/accounts/hd.go new file mode 100644 index 0000000..cab830e --- /dev/null +++ b/accounts/hd.go @@ -0,0 +1,180 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "strings" +) + +// DefaultRootDerivationPath is the root path to which custom derivation endpoints +// are appended. As such, the first account will be at m/44'/60'/0'/0, the second +// at m/44'/60'/0'/1, etc. +var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DefaultBaseDerivationPath is the base path from which custom derivation endpoints +// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second +// at m/44'/60'/0'/0/1, etc. +var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0} + +// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation +// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the +// second at m/44'/60'/0'/1, etc. +var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DerivationPath represents the computer friendly version of a hierarchical +// deterministic wallet account derivation path. +// +// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// defines derivation paths to be of the form: +// +// m / purpose' / coin_type' / account' / change / address_index +// +// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and +// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns +// the `coin_type` 60' (or 0x8000003C) to Ethereum. +// +// The root path for Ethereum is m/44'/60'/0'/0 according to the specification +// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone +// yet whether accounts should increment the last component or the children of +// that. We will go with the simpler approach of incrementing the last component. +type DerivationPath []uint32 + +// ParseDerivationPath converts a user specified derivation path string to the +// internal binary representation. +// +// Full derivation paths need to start with the `m/` prefix, relative derivation +// paths (which will get appended to the default root path) must not have prefixes +// in front of the first element. Whitespace is ignored. +func ParseDerivationPath(path string) (DerivationPath, error) { + var result DerivationPath + + // Handle absolute or relative paths + components := strings.Split(path, "/") + switch { + case len(components) == 0: + return nil, errors.New("empty derivation path") + + case strings.TrimSpace(components[0]) == "": + return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones") + + case strings.TrimSpace(components[0]) == "m": + components = components[1:] + + default: + result = append(result, DefaultRootDerivationPath...) + } + // All remaining components are relative, append one by one + if len(components) == 0 { + return nil, errors.New("empty derivation path") // Empty relative paths + } + for _, component := range components { + // Ignore any user added whitespace + component = strings.TrimSpace(component) + var value uint32 + + // Handle hardened paths + if strings.HasSuffix(component, "'") { + value = 0x80000000 + component = strings.TrimSpace(strings.TrimSuffix(component, "'")) + } + // Handle the non hardened component + bigval, ok := new(big.Int).SetString(component, 0) + if !ok { + return nil, fmt.Errorf("invalid component: %s", component) + } + max := math.MaxUint32 - value + if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 { + if value == 0 { + return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max) + } + return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max) + } + value += uint32(bigval.Uint64()) + + // Append and repeat + result = append(result, value) + } + return result, nil +} + +// String implements the stringer interface, converting a binary derivation path +// to its canonical representation. +func (path DerivationPath) String() string { + result := "m" + for _, component := range path { + var hardened bool + if component >= 0x80000000 { + component -= 0x80000000 + hardened = true + } + result = fmt.Sprintf("%s/%d", result, component) + if hardened { + result += "'" + } + } + return result +} + +// MarshalJSON turns a derivation path into its json-serialized string +func (path DerivationPath) MarshalJSON() ([]byte, error) { + return json.Marshal(path.String()) +} + +// UnmarshalJSON a json-serialized string back into a derivation path +func (path *DerivationPath) UnmarshalJSON(b []byte) error { + var dp string + var err error + if err = json.Unmarshal(b, &dp); err != nil { + return err + } + *path, err = ParseDerivationPath(dp) + return err +} + +// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component: +// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N. +func DefaultIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[len(path)-1]-- + return func() DerivationPath { + path[len(path)-1]++ + return path + } +} + +// LedgerLiveIterator creates a bip44 path iterator for Ledger Live. +// Ledger Live increments the third component rather than the fifth component +// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0. +func LedgerLiveIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[2]-- + return func() DerivationPath { + // ledgerLivePathIterator iterates on the third component + path[2]++ + return path + } +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go new file mode 100644 index 0000000..756aebd --- /dev/null +++ b/accounts/hd_test.go @@ -0,0 +1,118 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "fmt" + "reflect" + "testing" +) + +// Tests that HD derivation paths can be correctly parsed into our internal binary +// representation. +func TestHDPathParsing(t *testing.T) { + tests := []struct { + input string + output DerivationPath + }{ + // Plain absolute derivation paths + {"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Plain relative derivation paths + {"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Hexadecimal absolute derivation paths + {"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Hexadecimal relative derivation paths + {"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Weird inputs just to ensure they work + {" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + + // Invalid derivation paths + {"", nil}, // Empty relative derivation path + {"m", nil}, // Empty absolute derivation path + {"m/", nil}, // Missing last derivation component + {"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error + {"m/2147483648'", nil}, // Overflows 32 bit integer + {"m/-1'", nil}, // Cannot contain negative number + } + for i, tt := range tests { + if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { + t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output) + } else if path == nil && err == nil { + t.Errorf("test %d: nil path and error: %v", i, err) + } + } +} + +func testDerive(t *testing.T, next func() DerivationPath, expected []string) { + t.Helper() + for i, want := range expected { + if have := next(); fmt.Sprintf("%v", have) != want { + t.Errorf("step %d, have %v, want %v", i, have, want) + } + } +} + +func TestHdPathIteration(t *testing.T) { + testDerive(t, DefaultIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", + "m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3", + "m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5", + "m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7", + "m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9", + }) + + testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath), + []string{ + "m/44'/60'/0'/0", "m/44'/60'/0'/1", + "m/44'/60'/0'/2", "m/44'/60'/0'/3", + "m/44'/60'/0'/4", "m/44'/60'/0'/5", + "m/44'/60'/0'/6", "m/44'/60'/0'/7", + "m/44'/60'/0'/8", "m/44'/60'/0'/9", + }) + + testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0", + "m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0", + "m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0", + "m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0", + "m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0", + }) +} diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go new file mode 100644 index 0000000..1e1d957 --- /dev/null +++ b/accounts/keystore/account_cache.go @@ -0,0 +1,317 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + mapset "github.com/deckarep/golang-set/v2" +) + +// Minimum amount of time between cache reloads. This limit applies if the platform does +// not support change notifications. It also applies if the keystore directory does not +// exist yet, the code will attempt to create a watcher at most this often. +const minReloadInterval = 2 * time.Second + +type accountsByURL []accounts.Account + +func (s accountsByURL) Len() int { return len(s) } +func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 } +func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// AmbiguousAddrError is returned when attempting to unlock +// an address for which more than one file exists. +type AmbiguousAddrError struct { + Addr types.Address + Matches []accounts.Account +} + +func (err *AmbiguousAddrError) Error() string { + files := "" + for i, a := range err.Matches { + files += a.URL.Path + if i < len(err.Matches)-1 { + files += ", " + } + } + return fmt.Sprintf("multiple keys match address (%s)", files) +} + +// accountCache is a live index of all accounts in the keystore. +type accountCache struct { + keydir string + watcher *watcher + mu sync.Mutex + all accountsByURL + byAddr map[types.Address][]accounts.Account + throttle *time.Timer + notify chan struct{} + fileC fileCache +} + +func newAccountCache(keydir string) (*accountCache, chan struct{}) { + ac := &accountCache{ + keydir: keydir, + byAddr: make(map[types.Address][]accounts.Account), + notify: make(chan struct{}, 1), + fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()}, + } + ac.watcher = newWatcher(ac) + return ac, ac.notify +} + +func (ac *accountCache) accounts() []accounts.Account { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + cpy := make([]accounts.Account, len(ac.all)) + copy(cpy, ac.all) + return cpy +} + +func (ac *accountCache) hasAddress(addr types.Address) bool { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + return len(ac.byAddr[addr]) > 0 +} + +func (ac *accountCache) add(newAccount accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) + if i < len(ac.all) && ac.all[i] == newAccount { + return + } + // newAccount is not in the cache. + ac.all = append(ac.all, accounts.Account{}) + copy(ac.all[i+1:], ac.all[i:]) + ac.all[i] = newAccount + ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) +} + +// note: removed needs to be unique here (i.e. both File and Address must be set). +func (ac *accountCache) delete(removed accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + ac.all = removeAccount(ac.all, removed) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } +} + +// deleteByFile removes an account referenced by the given path. +func (ac *accountCache) deleteByFile(path string) { + ac.mu.Lock() + defer ac.mu.Unlock() + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) + + if i < len(ac.all) && ac.all[i].URL.Path == path { + removed := ac.all[i] + ac.all = append(ac.all[:i], ac.all[i+1:]...) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } + } +} + +// watcherStarted returns true if the watcher loop started running (even if it +// has since also ended). +func (ac *accountCache) watcherStarted() bool { + ac.mu.Lock() + defer ac.mu.Unlock() + return ac.watcher.running || ac.watcher.runEnded +} + +func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { + for i := range slice { + if slice[i] == elem { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} + +// find returns the cached account for address if there is a unique match. +// The exact matching rules are explained by the documentation of accounts.Account. +// Callers must hold ac.mu. +func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { + // Limit search to address candidates if possible. + matches := ac.all + if (a.Address != types.Address{}) { + matches = ac.byAddr[a.Address] + } + if a.URL.Path != "" { + // If only the basename is specified, complete the path. + if !strings.ContainsRune(a.URL.Path, filepath.Separator) { + a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) + } + for i := range matches { + if matches[i].URL == a.URL { + return matches[i], nil + } + } + if (a.Address == types.Address{}) { + return accounts.Account{}, ErrNoMatch + } + } + switch len(matches) { + case 1: + return matches[0], nil + case 0: + return accounts.Account{}, ErrNoMatch + default: + err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} + copy(err.Matches, matches) + sort.Sort(accountsByURL(err.Matches)) + return accounts.Account{}, err + } +} + +func (ac *accountCache) maybeReload() { + ac.mu.Lock() + + if ac.watcher.running { + ac.mu.Unlock() + return // A watcher is running and will keep the cache up-to-date. + } + if ac.throttle == nil { + ac.throttle = time.NewTimer(0) + } else { + select { + case <-ac.throttle.C: + default: + ac.mu.Unlock() + return // The cache was reloaded recently. + } + } + // No watcher running, start it. + ac.watcher.start() + ac.throttle.Reset(minReloadInterval) + ac.mu.Unlock() + err := ac.scanAccounts() + if err != nil { + return + } +} + +func (ac *accountCache) close() { + ac.mu.Lock() + ac.watcher.close() + if ac.throttle != nil { + ac.throttle.Stop() + } + if ac.notify != nil { + close(ac.notify) + ac.notify = nil + } + ac.mu.Unlock() +} + +// scanAccounts checks if any changes have occurred on the filesystem, and +// updates the account cache accordingly +func (ac *accountCache) scanAccounts() error { + // Scan the entire folder metadata for file changes + creates, deletes, updates, err := ac.fileC.scan(ac.keydir) + if err != nil { + log.Debug("Failed to reload keystore contents", "err", err) + return err + } + if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { + return nil + } + // Create a helper method to scan the contents of the key files + var ( + buf = new(bufio.Reader) + key struct { + Address string `json:"address"` + } + ) + readAccount := func(path string) *accounts.Account { + fd, err := os.Open(path) + if err != nil { + //log.Trace("Failed to open keystore file", "path", path, "err", err) + log.Debug("Failed to open keystore file", "path", path, "err", err) + return nil + } + defer func(fd *os.File) { + err := fd.Close() + if err != nil { + return + } + }(fd) + buf.Reset(fd) + // Parse the address. + key.Address = "" + err = json.NewDecoder(buf).Decode(&key) + addr := types.HexToAddress(key.Address) + switch { + case err != nil: + log.Debug("Failed to decode keystore key", "path", path, "err", err) + case addr == types.Address{}: + log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") + default: + return &accounts.Account{ + Address: addr, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, + } + } + return nil + } + // Process all the file diffs + start := time.Now() + + for _, path := range creates.ToSlice() { + if a := readAccount(path); a != nil { + ac.add(*a) + } + } + for _, path := range deletes.ToSlice() { + ac.deleteByFile(path) + } + for _, path := range updates.ToSlice() { + ac.deleteByFile(path) + if a := readAccount(path); a != nil { + ac.add(*a) + } + } + end := time.Now() + + select { + case ac.notify <- struct{}{}: + default: + } + log.Trace("Handled keystore changes", "time", end.Sub(start)) + return nil +} diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go new file mode 100644 index 0000000..19bf2ea --- /dev/null +++ b/accounts/keystore/account_cache_test.go @@ -0,0 +1,404 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "fmt" + "math/rand" + "os" + "path/filepath" + "reflect" + "sort" + "testing" + "time" + + "github.com/astranetworld/ast/accounts" + common "github.com/astranetworld/ast/common/types" + "github.com/cespare/cp" + "github.com/davecgh/go-spew/spew" +) + +var ( + cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) + cachetestAccounts = []accounts.Account{ + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, + }, + } +) + +func TestWatchNewFile(t *testing.T) { + t.Parallel() + + dir, ks := tmpKeyStore(t, false) + + // Ensure the watcher is started before adding any files. + ks.Accounts() + time.Sleep(1000 * time.Millisecond) + + // Move in the files. + wantAccounts := make([]accounts.Account, len(cachetestAccounts)) + for i := range cachetestAccounts { + wantAccounts[i] = accounts.Account{ + Address: cachetestAccounts[i].Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))}, + } + if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil { + t.Fatal(err) + } + } + + // ks should see the accounts. + var list []accounts.Account + for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + t.Fatalf("wasn't notified of new accounts") + } + return + } + time.Sleep(d) + } + t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts)) +} + +func TestWatchNoDir(t *testing.T) { + t.Parallel() + + // Create ks but not the directory that it watches. + rand.Seed(time.Now().UnixNano()) + dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int())) + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + time.Sleep(100 * time.Millisecond) + + // Create the directory and copy a key file into it. + os.MkdirAll(dir, 0700) + defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + t.Fatalf("wasn't notified of new accounts") + } + return + } + time.Sleep(d) + } + t.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +func TestCacheInitialReload(t *testing.T) { + cache, _ := newAccountCache(cachetestDir) + accounts := cache.accounts() + if !reflect.DeepEqual(accounts, cachetestAccounts) { + t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) + } +} + +func TestCacheAddDeleteOrder(t *testing.T) { + cache, _ := newAccountCache("testdata/no-such-dir") + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, + }, + { + Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, + }, + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, + }, + } + for _, a := range accs { + cache.add(a) + } + // Add some of them twice to check that they don't get reinserted. + cache.add(accs[0]) + cache.add(accs[2]) + + // Check that the account list is sorted by filename. + wantAccounts := make([]accounts.Account, len(accs)) + copy(wantAccounts, accs) + sort.Sort(accountsByURL(wantAccounts)) + list := cache.accounts() + if !reflect.DeepEqual(list, wantAccounts) { + t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) + } + for _, a := range accs { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { + t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) + } + + // Delete a few keys from the cache. + for i := 0; i < len(accs); i += 2 { + cache.delete(wantAccounts[i]) + } + cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) + + // Check content again after deletion. + wantAccountsAfterDelete := []accounts.Account{ + wantAccounts[1], + wantAccounts[3], + wantAccounts[5], + } + list = cache.accounts() + if !reflect.DeepEqual(list, wantAccountsAfterDelete) { + t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) + } + for _, a := range wantAccountsAfterDelete { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(wantAccounts[0].Address) { + t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) + } +} + +func TestCacheFind(t *testing.T) { + dir := filepath.Join("testdata", "dir") + cache, _ := newAccountCache(dir) + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, + }, + } + for _, a := range accs { + cache.add(a) + } + + nomatchAccount := accounts.Account{ + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, + } + tests := []struct { + Query accounts.Account + WantResult accounts.Account + WantError error + }{ + // by address + {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, + // by file + {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, + // by basename + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, + // by file and address + {Query: accs[0], WantResult: accs[0]}, + // ambiguous address, tie resolved by file + {Query: accs[2], WantResult: accs[2]}, + // ambiguous address error + { + Query: accounts.Account{Address: accs[2].Address}, + WantError: &AmbiguousAddrError{ + Addr: accs[2].Address, + Matches: []accounts.Account{accs[2], accs[3]}, + }, + }, + // no match error + {Query: nomatchAccount, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, + {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, + } + for i, test := range tests { + a, err := cache.find(test.Query) + if !reflect.DeepEqual(err, test.WantError) { + t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) + continue + } + if a != test.WantResult { + t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) + continue + } + } +} + +func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { + var list []accounts.Account + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + return fmt.Errorf("wasn't notified of new accounts") + } + return nil + } + time.Sleep(d) + } + return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +// TestUpdatedKeyfileContents tests that updating the contents of a keystore file +// is noticed by the watcher, and the account cache is updated accordingly +func TestUpdatedKeyfileContents(t *testing.T) { + t.Parallel() + + // Create a temporary keystore to test with + rand.Seed(time.Now().UnixNano()) + dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int())) + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + time.Sleep(100 * time.Millisecond) + + // Create the directory and copy a key file into it. + os.MkdirAll(dir, 0700) + defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") + + // Place one of our testfiles in there + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents + if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[1]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("First replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents again + if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[2]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("Second replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after os.WriteFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents with crap + if err := os.WriteFile(file, []byte("foo"), 0600); err != nil { + t.Fatal(err) + return + } + if err := waitForAccounts([]accounts.Account{}, ks); err != nil { + t.Errorf("Emptying account file failed") + t.Error(err) + return + } +} + +// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. +func forceCopyFile(dst, src string) error { + data, err := os.ReadFile(src) + if err != nil { + return err + } + return os.WriteFile(dst, data, 0644) +} diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go new file mode 100644 index 0000000..45fa87b --- /dev/null +++ b/accounts/keystore/file_cache.go @@ -0,0 +1,106 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "github.com/astranetworld/ast/log" + "os" + "path/filepath" + "strings" + "sync" + "time" + + mapset "github.com/deckarep/golang-set/v2" +) + +// fileCache is a cache of files seen during scan of keystore. +type fileCache struct { + all mapset.Set[string] // Set of all files from the keystore folder + lastMod time.Time // Last time instance when a file was modified + mu sync.Mutex +} + +// scan performs a new scan on the given directory, compares against the already +// cached filenames, and returns file sets: creates, deletes, updates. +func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) { + t0 := time.Now() + + // List all the files from the keystore folder + files, err := os.ReadDir(keyDir) + if err != nil { + return nil, nil, nil, err + } + t1 := time.Now() + + fc.mu.Lock() + defer fc.mu.Unlock() + + // Iterate all the files and gather their metadata + all := mapset.NewThreadUnsafeSet[string]() + mods := mapset.NewThreadUnsafeSet[string]() + + var newLastMod time.Time + for _, fi := range files { + path := filepath.Join(keyDir, fi.Name()) + // Skip any non-key files from the folder + if nonKeyFile(fi) { + //log.Trace("Ignoring file on account scan", "path", path) + log.Debug("Ignoring file on account scan", "path", path) + continue + } + // Gather the set of all and freshly modified files + all.Add(path) + + info, err := fi.Info() + if err != nil { + return nil, nil, nil, err + } + modified := info.ModTime() + if modified.After(fc.lastMod) { + mods.Add(path) + } + if modified.After(newLastMod) { + newLastMod = modified + } + } + t2 := time.Now() + + // Update the tracked files and return the three sets + deletes := fc.all.Difference(all) // Deletes = previous - current + creates := all.Difference(fc.all) // Creates = current - previous + updates := mods.Difference(creates) // Updates = modified - creates + + fc.all, fc.lastMod = all, newLastMod + t3 := time.Now() + + // Report on the scanning stats and return + log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) + return creates, deletes, updates, nil +} + +// nonKeyFile ignores editor backups, hidden files and folders/symlinks. +func nonKeyFile(fi os.DirEntry) bool { + // Skip editor backups and UNIX-style hidden files. + if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { + return true + } + // Skip misc special files, directories (yes, symlinks too). + if fi.IsDir() || !fi.Type().IsRegular() { + return true + } + return false +} diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go new file mode 100644 index 0000000..d6adbf1 --- /dev/null +++ b/accounts/keystore/key.go @@ -0,0 +1,236 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package keystore + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "github.com/google/uuid" +) + +const ( + version = 3 +) + +type Key struct { + Id uuid.UUID // Version 4 "random" for unique id not derived from key data + // to simplify lookups we also store the address + Address types.Address + // we only store privkey as pubkey/address can be derived from it + // privkey in this struct is always in plaintext + PrivateKey *ecdsa.PrivateKey +} + +type keyStore interface { + // Loads and decrypts the key from disk. + GetKey(addr types.Address, filename string, auth string) (*Key, error) + // Writes and encrypts the key. + StoreKey(filename string, k *Key, auth string) error + // Joins filename with the key directory unless it is already absolute. + JoinPath(filename string) string +} + +type plainKeyJSON struct { + Address string `json:"address"` + PrivateKey string `json:"privatekey"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV3 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV1 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version string `json:"version"` +} + +type CryptoJSON struct { + Cipher string `json:"cipher"` + CipherText string `json:"ciphertext"` + CipherParams cipherparamsJSON `json:"cipherparams"` + KDF string `json:"kdf"` + KDFParams map[string]interface{} `json:"kdfparams"` + MAC string `json:"mac"` +} + +type cipherparamsJSON struct { + IV string `json:"iv"` +} + +func (k *Key) MarshalJSON() (j []byte, err error) { + jStruct := plainKeyJSON{ + hex.EncodeToString(k.Address[:]), + hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), + k.Id.String(), + version, + } + j, err = json.Marshal(jStruct) + return j, err +} + +func (k *Key) UnmarshalJSON(j []byte) (err error) { + keyJSON := new(plainKeyJSON) + err = json.Unmarshal(j, &keyJSON) + if err != nil { + return err + } + + u := new(uuid.UUID) + *u, err = uuid.Parse(keyJSON.Id) + if err != nil { + return err + } + k.Id = *u + addr, err := hex.DecodeString(keyJSON.Address) + if err != nil { + return err + } + privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) + if err != nil { + return err + } + + k.Address = types.BytesToAddress(addr) + k.PrivateKey = privkey + + return nil +} + +func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf("Could not create random uuid: %v", err)) + } + key := &Key{ + Id: id, + Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), + PrivateKey: privateKeyECDSA, + } + return key +} + +// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit +// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we +// retry until the first byte is 0. +func NewKeyForDirectICAP(rand io.Reader) *Key { + randBytes := make([]byte, 64) + _, err := rand.Read(randBytes) + if err != nil { + panic("key generation: could not read from random source: " + err.Error()) + } + reader := bytes.NewReader(randBytes) + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) + if err != nil { + panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) + } + key := newKeyFromECDSA(privateKeyECDSA) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + return NewKeyForDirectICAP(rand) + } + return key +} + +func newKey(rand io.Reader) (*Key, error) { + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) + if err != nil { + return nil, err + } + return newKeyFromECDSA(privateKeyECDSA), nil +} + +func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { + key, err := newKey(rand) + if err != nil { + return nil, accounts.Account{}, err + } + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, + } + if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { + zeroKey(key.PrivateKey) + return nil, a, err + } + return key, a, err +} + +func writeTemporaryKeyFile(file string, content []byte) (string, error) { + // Create the keystore directory with appropriate permissions + // in case it is not present yet. + const dirPerm = 0700 + if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { + return "", err + } + // Atomic write: create a temporary hidden file first + // then move it into place. TempFile assigns mode 0600. + f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp") + if err != nil { + return "", err + } + if _, err := f.Write(content); err != nil { + f.Close() + os.Remove(f.Name()) + return "", err + } + f.Close() + return f.Name(), nil +} + +func writeKeyFile(file string, content []byte) error { + name, err := writeTemporaryKeyFile(file, content) + if err != nil { + return err + } + return os.Rename(name, file) +} + +// keyFileName implements the naming convention for keyfiles: +// UTC---
+func keyFileName(keyAddr types.Address) string { + ts := time.Now().UTC() + return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) +} + +func toISO8601(t time.Time) string { + var tz string + name, offset := t.Zone() + if name == "UTC" { + tz = "Z" + } else { + tz = fmt.Sprintf("%03d00", offset/3600) + } + return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", + t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) +} diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go new file mode 100644 index 0000000..ae8d196 --- /dev/null +++ b/accounts/keystore/keystore.go @@ -0,0 +1,515 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package keystore implements encrypted storage of secp256k1 private keys. +// +// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification. +// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information. +package keystore + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "math/big" + "os" + "path/filepath" + "reflect" + "runtime" + "sync" + "time" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + event "github.com/astranetworld/ast/modules/event/v2" +) + +var ( + ErrLocked = accounts.NewAuthNeededError("password or unlock") + ErrNoMatch = errors.New("no key for given address or file") + ErrDecrypt = errors.New("could not decrypt key with given password") + + // ErrAccountAlreadyExists is returned if an account attempted to import is + // already present in the keystore. + ErrAccountAlreadyExists = errors.New("account already exists") +) + +// KeyStoreType is the reflect type of a keystore backend. +var KeyStoreType = reflect.TypeOf(&KeyStore{}) + +// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs. +const KeyStoreScheme = "keystore" + +// Maximum time between wallet refreshes (if filesystem notifications don't work). +const walletRefreshCycle = 3 * time.Second + +// KeyStore manages a key storage directory on disk. +type KeyStore struct { + storage keyStore // Storage backend, might be cleartext or encrypted + cache *accountCache // In-memory account cache over the filesystem storage + changes chan struct{} // Channel receiving change notifications from the cache + unlocked map[types.Address]*unlocked // Currently unlocked account (decrypted private keys) + + wallets []accounts.Wallet // Wallet wrappers around the individual key files + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + mu sync.RWMutex + importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing +} + +type unlocked struct { + *Key + abort chan struct{} +} + +// NewKeyStore creates a keystore for the given directory. +func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}} + ks.init(keydir) + return ks +} + +// NewPlaintextKeyStore creates a keystore for the given directory. +// Deprecated: Use NewKeyStore. +func NewPlaintextKeyStore(keydir string) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{storage: &keyStorePlain{keydir}} + ks.init(keydir) + return ks +} + +func (ks *KeyStore) init(keydir string) { + // Lock the mutex since the account cache might call back with events + ks.mu.Lock() + defer ks.mu.Unlock() + + // Initialize the set of unlocked keys and the account cache + ks.unlocked = make(map[types.Address]*unlocked) + ks.cache, ks.changes = newAccountCache(keydir) + + // TODO: In order for this finalizer to work, there must be no references + // to ks. addressCache doesn't keep a reference but unlocked keys do, + // so the finalizer will not trigger until all timed unlocks have expired. + runtime.SetFinalizer(ks, func(m *KeyStore) { + m.cache.close() + }) + // Create the initial list of wallets from the cache + accs := ks.cache.accounts() + ks.wallets = make([]accounts.Wallet, len(accs)) + for i := 0; i < len(accs); i++ { + ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} + } +} + +// Wallets implements accounts.Backend, returning all single-key wallets from the +// keystore directory. +func (ks *KeyStore) Wallets() []accounts.Wallet { + // Make sure the list of wallets is in sync with the account cache + ks.refreshWallets() + + ks.mu.RLock() + defer ks.mu.RUnlock() + + cpy := make([]accounts.Wallet, len(ks.wallets)) + copy(cpy, ks.wallets) + return cpy +} + +// refreshWallets retrieves the current account list and based on that does any +// necessary wallet refreshes. +func (ks *KeyStore) refreshWallets() { + // Retrieve the current list of accounts + ks.mu.Lock() + accs := ks.cache.accounts() + + // Transform the current list of wallets into the new one + var ( + wallets = make([]accounts.Wallet, 0, len(accs)) + events []accounts.WalletEvent + ) + + for _, account := range accs { + // Drop wallets while they were in front of the next account + for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 { + events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped}) + ks.wallets = ks.wallets[1:] + } + // If there are no more wallets or the account is before the next, wrap new wallet + if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 { + wallet := &keystoreWallet{account: account, keystore: ks} + + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + wallets = append(wallets, wallet) + continue + } + // If the account is the same as the first wallet, keep it + if ks.wallets[0].Accounts()[0] == account { + wallets = append(wallets, ks.wallets[0]) + ks.wallets = ks.wallets[1:] + continue + } + } + // Drop any leftover wallets and set the new batch + for _, wallet := range ks.wallets { + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + } + ks.wallets = wallets + ks.mu.Unlock() + + // Fire all wallet events and return + for _, event := range events { + ks.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of keystore wallets. +func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + ks.mu.Lock() + defer ks.mu.Unlock() + + // Subscribe the caller and track the subscriber count + sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !ks.updating { + ks.updating = true + go ks.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets stored in +// the keystore, and for firing wallet addition/removal events. It listens for +// account change events from the underlying account cache, and also periodically +// forces a manual refresh (only triggers for systems where the filesystem notifier +// is not running). +func (ks *KeyStore) updater() { + for { + // Wait for an account update or a refresh timeout + select { + case <-ks.changes: + case <-time.After(walletRefreshCycle): + } + // Run the wallet refresher + ks.refreshWallets() + + // If all our subscribers left, stop the updater + ks.mu.Lock() + if ks.updateScope.Count() == 0 { + ks.updating = false + ks.mu.Unlock() + return + } + ks.mu.Unlock() + } +} + +// HasAddress reports whether a key with the given address is present. +func (ks *KeyStore) HasAddress(addr types.Address) bool { + return ks.cache.hasAddress(addr) +} + +// Accounts returns all key files present in the directory. +func (ks *KeyStore) Accounts() []accounts.Account { + return ks.cache.accounts() +} + +// Delete deletes the key matched by account if the passphrase is correct. +// If the account contains no filename, the address must match a unique key. +func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { + // Decrypting the key isn't really necessary, but we do + // it anyway to check the password and zero out the key + // immediately afterwards. + a, key, err := ks.getDecryptedKey(a, passphrase) + if key != nil { + zeroKey(key.PrivateKey) + } + if err != nil { + return err + } + // The order is crucial here. The key is dropped from the + // cache after the file is gone so that a reload happening in + // between won't insert it into the cache again. + err = os.Remove(a.URL.Path) + if err == nil { + ks.cache.delete(a) + ks.refreshWallets() + } + return err +} + +// SignHash calculates a ECDSA signature for the given hash. The produced +// signature is in the [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Sign the hash using plain ECDSA operations + return crypto.Sign(hash, unlockedKey.PrivateKey) +} + +// SignTx signs the given transaction with the requested account. +func (ks *KeyStore) SignTx(a accounts.Account, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Depending on the presence of the chain ID, sign with 2718 or homestead + signer := transaction.LatestSignerForChainID(chainID) + return transaction.SignTx(tx, signer, unlockedKey.PrivateKey) +} + +// SignHashWithPassphrase signs hash if the private key matching the given address +// can be decrypted with the given passphrase. The produced signature is in the +// [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + return crypto.Sign(hash, key.PrivateKey) +} + +// SignTxWithPassphrase signs the transaction if the private key matching the +// given address can be decrypted with the given passphrase. +func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + // Depending on the presence of the chain ID, sign with or without replay protection. + signer := transaction.LatestSignerForChainID(chainID) + return transaction.SignTx(tx, signer, key.PrivateKey) +} + +// Unlock unlocks the given account indefinitely. +func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error { + return ks.TimedUnlock(a, passphrase, 0) +} + +// Lock removes the private key with the given address from memory. +func (ks *KeyStore) Lock(addr types.Address) error { + ks.mu.Lock() + if unl, found := ks.unlocked[addr]; found { + ks.mu.Unlock() + ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) + } else { + ks.mu.Unlock() + } + return nil +} + +// TimedUnlock unlocks the given account with the passphrase. The account +// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account +// until the program exits. The account must match a unique key file. +// +// If the account address is already unlocked for a duration, TimedUnlock extends or +// shortens the active unlock timeout. If the address was previously unlocked +// indefinitely the timeout is not altered. +func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + + ks.mu.Lock() + defer ks.mu.Unlock() + u, found := ks.unlocked[a.Address] + if found { + if u.abort == nil { + // The address was unlocked indefinitely, so unlocking + // it with a timeout would be confusing. + zeroKey(key.PrivateKey) + return nil + } + // Terminate the expire goroutine and replace it below. + close(u.abort) + } + if timeout > 0 { + u = &unlocked{Key: key, abort: make(chan struct{})} + go ks.expire(a.Address, u, timeout) + } else { + u = &unlocked{Key: key} + } + ks.unlocked[a.Address] = u + return nil +} + +// Find resolves the given account into a unique entry in the keystore. +func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) { + ks.cache.maybeReload() + ks.cache.mu.Lock() + a, err := ks.cache.find(a) + ks.cache.mu.Unlock() + return a, err +} + +func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) { + a, err := ks.Find(a) + if err != nil { + return a, nil, err + } + key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) + return a, key, err +} + +func (ks *KeyStore) expire(addr types.Address, u *unlocked, timeout time.Duration) { + t := time.NewTimer(timeout) + defer t.Stop() + select { + case <-u.abort: + // just quit + case <-t.C: + ks.mu.Lock() + // only drop if it's still the same key instance that dropLater + // was launched with. we can check that using pointer equality + // because the map stores a new pointer every time the key is + // unlocked. + if ks.unlocked[addr] == u { + zeroKey(u.PrivateKey) + delete(ks.unlocked, addr) + } + ks.mu.Unlock() + } +} + +// NewAccount generates a new key and stores it into the key directory, +// encrypting it with the passphrase. +func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) { + _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase) + if err != nil { + return accounts.Account{}, err + } + // Add the account to the cache immediately rather + // than waiting for file system notifications to pick it up. + ks.cache.add(account) + ks.refreshWallets() + return account, nil +} + +// Export exports as a JSON key, encrypted with newPassphrase. +func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + var N, P int + if store, ok := ks.storage.(*keyStorePassphrase); ok { + N, P = store.scryptN, store.scryptP + } else { + N, P = StandardScryptN, StandardScryptP + } + return EncryptKey(key, newPassphrase, N, P) +} + +// Import stores the given encrypted JSON key into the key directory. +func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) { + key, err := DecryptKey(keyJSON, passphrase) + if key != nil && key.PrivateKey != nil { + defer zeroKey(key.PrivateKey) + } + if err != nil { + return accounts.Account{}, err + } + ks.importMu.Lock() + defer ks.importMu.Unlock() + + if ks.cache.hasAddress(key.Address) { + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists + } + return ks.importKey(key, newPassphrase) +} + +// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. +func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { + ks.importMu.Lock() + defer ks.importMu.Unlock() + + key := newKeyFromECDSA(priv) + if ks.cache.hasAddress(key.Address) { + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists + } + return ks.importKey(key, passphrase) +} + +func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) { + a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}} + if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { + return accounts.Account{}, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// Update changes the passphrase of an existing account. +func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) +} + +// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores +// a key file in the key directory. The key file is encrypted with the same passphrase. +func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) { + a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase) + if err != nil { + return a, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// isUpdating returns whether the event notification loop is running. +// This method is mainly meant for tests. +func (ks *KeyStore) isUpdating() bool { + ks.mu.RLock() + defer ks.mu.RUnlock() + return ks.updating +} + +// zeroKey zeroes a private key in memory. +func zeroKey(k *ecdsa.PrivateKey) { + b := k.D.Bits() + for i := range b { + b[i] = 0 + } +} diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go new file mode 100644 index 0000000..aeff858 --- /dev/null +++ b/accounts/keystore/keystore_test.go @@ -0,0 +1,456 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "math/rand" + "os" + "runtime" + "sort" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/avm/common" + event "github.com/astranetworld/ast/modules/event/v2" +) + +var testSigData = make([]byte, 32) + +func TestKeyStore(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + + a, err := ks.NewAccount("foo") + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(a.URL.Path, dir) { + t.Errorf("account file %s doesn't have dir prefix", a.URL) + } + stat, err := os.Stat(a.URL.Path) + if err != nil { + t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) + } + if runtime.GOOS != "windows" && stat.Mode() != 0600 { + t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) + } + if !ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true", a.Address) + } + if err := ks.Update(a, "foo", "bar"); err != nil { + t.Errorf("Update error: %v", err) + } + if err := ks.Delete(a, "bar"); err != nil { + t.Errorf("Delete error: %v", err) + } + if common.FileExist(a.URL.Path) { + t.Errorf("account file %s should be gone after Delete", a.URL) + } + if ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) + } +} + +func TestSign(t *testing.T) { + _, ks := tmpKeyStore(t, true) + + pass := "" // not used but required by API + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + if err := ks.Unlock(a1, ""); err != nil { + t.Fatal(err) + } + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { + t.Fatal(err) + } +} + +func TestSignWithPassphrase(t *testing.T) { + _, ks := tmpKeyStore(t, true) + + pass := "passwd" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHashWithPassphrase to fail with invalid password") + } +} + +func TestTimedUnlock(t *testing.T) { + _, ks := tmpKeyStore(t, true) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase fails because account is locked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) + } + + // Signing with passphrase works + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +func TestOverrideUnlock(t *testing.T) { + _, ks := tmpKeyStore(t, false) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Unlock indefinitely. + if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // reset unlock to a shorter period, invalidates the previous unlock + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase still works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +// This test should fail under -race if signing races the expiration goroutine. +func TestSignRace(t *testing.T) { + _, ks := tmpKeyStore(t, false) + + // Create a test account. + a1, err := ks.NewAccount("") + if err != nil { + t.Fatal("could not create the test account", err) + } + + if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { + t.Fatal("could not unlock the test account", err) + } + end := time.Now().Add(500 * time.Millisecond) + for time.Now().Before(end) { + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { + return + } else if err != nil { + t.Errorf("Sign error: %v", err) + return + } + time.Sleep(1 * time.Millisecond) + } + t.Errorf("Account did not lock within the timeout") +} + +// Tests that the wallet notifier loop starts and stops correctly based on the +// addition and removal of wallet event subscriptions. +func TestWalletNotifierLifecycle(t *testing.T) { + // Create a temporary keystore to test with + _, ks := tmpKeyStore(t, false) + + // Ensure that the notification updater is not running yet + time.Sleep(250 * time.Millisecond) + ks.mu.RLock() + updating := ks.updating + ks.mu.RUnlock() + + if updating { + t.Errorf("wallet notifier running without subscribers") + } + // Subscribe to the wallet feed and ensure the updater boots up + updates := make(chan accounts.WalletEvent) + + subs := make([]event.Subscription, 2) + for i := 0; i < len(subs); i++ { + // Create a new subscription + subs[i] = ks.Subscribe(updates) + + // Ensure the notifier comes online + time.Sleep(250 * time.Millisecond) + ks.mu.RLock() + updating = ks.updating + ks.mu.RUnlock() + + if !updating { + t.Errorf("sub %d: wallet notifier not running after subscription", i) + } + } + // Unsubscribe and ensure the updater terminates eventually + for i := 0; i < len(subs); i++ { + // Close an existing subscription + subs[i].Unsubscribe() + + // Ensure the notifier shuts down at and only at the last close + for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ { + ks.mu.RLock() + updating = ks.updating + ks.mu.RUnlock() + + if i < len(subs)-1 && !updating { + t.Fatalf("sub %d: event notifier stopped prematurely", i) + } + if i == len(subs)-1 && !updating { + return + } + time.Sleep(250 * time.Millisecond) + } + } + t.Errorf("wallet notifier didn't terminate after unsubscribe") +} + +type walletEvent struct { + accounts.WalletEvent + a accounts.Account +} + +// Tests that wallet notifications and correctly fired when accounts are added +// or deleted from the keystore. +func TestWalletNotifications(t *testing.T) { + _, ks := tmpKeyStore(t, false) + + // Subscribe to the wallet feed and collect events. + var ( + events []walletEvent + updates = make(chan accounts.WalletEvent) + sub = ks.Subscribe(updates) + ) + defer sub.Unsubscribe() + go func() { + for { + select { + case ev := <-updates: + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + case <-sub.Err(): + close(updates) + return + } + } + }() + + // Randomly add and remove accounts. + var ( + live = make(map[types.Address]accounts.Account) + wantEvents []walletEvent + ) + for i := 0; i < 1024; i++ { + if create := len(live) == 0 || rand.Int()%4 > 0; create { + // Add a new account and ensure wallet notifications arrives + account, err := ks.NewAccount("") + if err != nil { + t.Fatalf("failed to create test account: %v", err) + } + live[account.Address] = account + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account}) + } else { + // Delete a random account. + var account accounts.Account + for _, a := range live { + account = a + break + } + if err := ks.Delete(account, ""); err != nil { + t.Fatalf("failed to delete test account: %v", err) + } + delete(live, account.Address) + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account}) + } + } + + // Shut down the event collector and check events. + sub.Unsubscribe() + for ev := range updates { + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + } + checkAccounts(t, live, ks.Wallets()) + checkEvents(t, wantEvents, events) +} + +// TestImportExport tests the import functionality of a keystore. +func TestImportECDSA(t *testing.T) { + _, ks := tmpKeyStore(t, true) + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", key) + } + if _, err = ks.ImportECDSA(key, "old"); err != nil { + t.Errorf("importing failed: %v", err) + } + if _, err = ks.ImportECDSA(key, "old"); err == nil { + t.Errorf("importing same key twice succeeded") + } + if _, err = ks.ImportECDSA(key, "new"); err == nil { + t.Errorf("importing same key twice succeeded") + } +} + +// TestImportECDSA tests the import and export functionality of a keystore. +func TestImportExport(t *testing.T) { + _, ks := tmpKeyStore(t, true) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + _, ks2 := tmpKeyStore(t, true) + if _, err = ks2.Import(json, "old", "old"); err == nil { + t.Errorf("importing with invalid password succeeded") + } + acc2, err := ks2.Import(json, "new", "new") + if err != nil { + t.Errorf("importing failed: %v", err) + } + if acc.Address != acc2.Address { + t.Error("imported account does not match exported account") + } + if _, err = ks2.Import(json, "new", "new"); err == nil { + t.Errorf("importing a key twice succeeded") + } +} + +// TestImportRace tests the keystore on races. +// This test should fail under -race if importing races. +func TestImportRace(t *testing.T) { + _, ks := tmpKeyStore(t, true) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + _, ks2 := tmpKeyStore(t, true) + var atom uint32 + var wg sync.WaitGroup + wg.Add(2) + for i := 0; i < 2; i++ { + go func() { + defer wg.Done() + if _, err := ks2.Import(json, "new", "new"); err != nil { + atomic.AddUint32(&atom, 1) + } + }() + } + wg.Wait() + if atom != 1 { + t.Errorf("Import is racy") + } +} + +// checkAccounts checks that all known live accounts are present in the wallet list. +func checkAccounts(t *testing.T, live map[types.Address]accounts.Account, wallets []accounts.Wallet) { + if len(live) != len(wallets) { + t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) + return + } + liveList := make([]accounts.Account, 0, len(live)) + for _, account := range live { + liveList = append(liveList, account) + } + sort.Sort(accountsByURL(liveList)) + for j, wallet := range wallets { + if accs := wallet.Accounts(); len(accs) != 1 { + t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) + } else if accs[0] != liveList[j] { + t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) + } + } +} + +// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times. +func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { + for _, wantEv := range want { + nmatch := 0 + for ; len(have) > 0; nmatch++ { + if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { + break + } + have = have[1:] + } + if nmatch == 0 { + t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) + } + } +} + +func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { + d := t.TempDir() + newKs := NewPlaintextKeyStore + if encrypted { + newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } + } + return d, newKs(d) +} diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go new file mode 100644 index 0000000..0151f44 --- /dev/null +++ b/accounts/keystore/passphrase.go @@ -0,0 +1,364 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +/* + +This key store behaves as KeyStorePlain with the difference that +the private key is encrypted and on disk uses another JSON encoding. + +The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + +*/ + +package keystore + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/math" + "github.com/astranetworld/ast/common/types" + "github.com/google/uuid" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/scrypt" +) + +const ( + keyHeaderKDF = "scrypt" + + // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptN = 1 << 18 + + // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptP = 1 + + // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptN = 1 << 12 + + // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptP = 6 + + scryptR = 8 + scryptDKLen = 32 +) + +type keyStorePassphrase struct { + keysDirPath string + scryptN int + scryptP int + // skipKeyFileVerification disables the security-feature which does + // reads and decrypts any newly created keyfiles. This should be 'false' in all + // cases except tests -- setting this to 'true' is not recommended. + skipKeyFileVerification bool +} + +func (ks keyStorePassphrase) GetKey(addr types.Address, filename, auth string) (*Key, error) { + // Load the key from the keystore and decrypt its contents + keyjson, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + key, err := DecryptKey(keyjson, auth) + if err != nil { + return nil, err + } + // Make sure we're really operating on the requested key (no swap attacks) + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) + } + return key, nil +} + +// StoreKey generates a key, encrypts with 'auth' and stores in the given directory +func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) { + _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth) + return a, err +} + +func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { + keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) + if err != nil { + return err + } + // Write into temporary file + tmpName, err := writeTemporaryKeyFile(filename, keyjson) + if err != nil { + return err + } + if !ks.skipKeyFileVerification { + // Verify that we can decrypt the file with the given password. + _, err = ks.GetKey(key.Address, tmpName, auth) + if err != nil { + msg := "An error was encountered when saving and verifying the keystore file. \n" + + "This indicates that the keystore is corrupted. \n" + + "The corrupted file is stored at \n%v\n" + + "Please file a ticket at:\n\n" + + "https://github.com/astranetworld/ast/issues." + + "The error was : %s" + //lint:ignore ST1005 This is a message for the user + return fmt.Errorf(msg, tmpName, err) + } + } + return os.Rename(tmpName, filename) +} + +func (ks keyStorePassphrase) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} + +// Encryptdata encrypts the data given as 'data' with the password 'auth'. +func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { + salt := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) + if err != nil { + return CryptoJSON{}, err + } + encryptKey := derivedKey[:16] + + iv := make([]byte, aes.BlockSize) // 16 + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + cipherText, err := aesCTRXOR(encryptKey, data, iv) + if err != nil { + return CryptoJSON{}, err + } + mac := crypto.Keccak256(derivedKey[16:32], cipherText) + + scryptParamsJSON := make(map[string]interface{}, 5) + scryptParamsJSON["n"] = scryptN + scryptParamsJSON["r"] = scryptR + scryptParamsJSON["p"] = scryptP + scryptParamsJSON["dklen"] = scryptDKLen + scryptParamsJSON["salt"] = hex.EncodeToString(salt) + cipherParamsJSON := cipherparamsJSON{ + IV: hex.EncodeToString(iv), + } + + cryptoStruct := CryptoJSON{ + Cipher: "aes-128-ctr", + CipherText: hex.EncodeToString(cipherText), + CipherParams: cipherParamsJSON, + KDF: keyHeaderKDF, + KDFParams: scryptParamsJSON, + MAC: hex.EncodeToString(mac), + } + return cryptoStruct, nil +} + +// EncryptKey encrypts a key using the specified scrypt parameters into a json +// blob that can be decrypted later on. +func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { + keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) + cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) + if err != nil { + return nil, err + } + encryptedKeyJSONV3 := encryptedKeyJSONV3{ + hex.EncodeToString(key.Address[:]), + cryptoStruct, + key.Id.String(), + version, + } + return json.Marshal(encryptedKeyJSONV3) +} + +// DecryptKey decrypts a key from a json blob, returning the private key itself. +func DecryptKey(keyjson []byte, auth string) (*Key, error) { + // Parse the json into a simple map to fetch the key version + m := make(map[string]interface{}) + if err := json.Unmarshal(keyjson, &m); err != nil { + return nil, err + } + // Depending on the version try to parse one way or another + var ( + keyBytes, keyId []byte + err error + ) + if version, ok := m["version"].(string); ok && version == "1" { + k := new(encryptedKeyJSONV1) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV1(k, auth) + } else { + k := new(encryptedKeyJSONV3) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV3(k, auth) + } + // Handle any decryption errors and return the key + if err != nil { + return nil, err + } + key := crypto.ToECDSAUnsafe(keyBytes) + id, err := uuid.FromBytes(keyId) + if err != nil { + return nil, err + } + return &Key{ + Id: id, + Address: crypto.PubkeyToAddress(key.PublicKey), + PrivateKey: key, + }, nil +} + +func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { + if cryptoJson.Cipher != "aes-128-ctr" { + return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher) + } + mac, err := hex.DecodeString(cryptoJson.MAC) + if err != nil { + return nil, err + } + + iv, err := hex.DecodeString(cryptoJson.CipherParams.IV) + if err != nil { + return nil, err + } + + cipherText, err := hex.DecodeString(cryptoJson.CipherText) + if err != nil { + return nil, err + } + + derivedKey, err := getKDFKey(cryptoJson, auth) + if err != nil { + return nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, ErrDecrypt + } + + plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) + if err != nil { + return nil, err + } + return plainText, err +} + +func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { + if keyProtected.Version != version { + return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) + } + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] + plainText, err := DecryptDataV3(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] + mac, err := hex.DecodeString(keyProtected.Crypto.MAC) + if err != nil { + return nil, nil, err + } + + iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) + if err != nil { + return nil, nil, err + } + + cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) + if err != nil { + return nil, nil, err + } + + derivedKey, err := getKDFKey(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, nil, ErrDecrypt + } + + plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { + authArray := []byte(auth) + salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) + if err != nil { + return nil, err + } + dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) + + if cryptoJSON.KDF == keyHeaderKDF { + n := ensureInt(cryptoJSON.KDFParams["n"]) + r := ensureInt(cryptoJSON.KDFParams["r"]) + p := ensureInt(cryptoJSON.KDFParams["p"]) + return scrypt.Key(authArray, salt, n, r, p, dkLen) + } else if cryptoJSON.KDF == "pbkdf2" { + c := ensureInt(cryptoJSON.KDFParams["c"]) + prf := cryptoJSON.KDFParams["prf"].(string) + if prf != "hmac-sha256" { + return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf) + } + key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) + return key, nil + } + + return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF) +} + +// TODO: can we do without this when unmarshalling dynamic JSON? +// why do integers in KDF params end up as float64 and not int after +// unmarshal? +func ensureInt(x interface{}) int { + res, ok := x.(int) + if !ok { + res = int(x.(float64)) + } + return res +} diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go new file mode 100644 index 0000000..c9af94a --- /dev/null +++ b/accounts/keystore/passphrase_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "os" + "testing" + + common "github.com/astranetworld/ast/common/types" +) + +const ( + veryLightScryptN = 2 + veryLightScryptP = 1 +) + +// Tests that a json key file can be decrypted and encrypted in multiple rounds. +func TestKeyEncryptDecrypt(t *testing.T) { + keyjson, err := os.ReadFile("testdata/very-light-scrypt.json") + if err != nil { + t.Fatal(err) + } + password := "" + address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8") + + // Do a few rounds of decryption and encryption + for i := 0; i < 3; i++ { + // Try a bad password first + if _, err := DecryptKey(keyjson, password+"bad"); err == nil { + t.Errorf("test %d: json key decrypted with bad password", i) + } + // Decrypt with the correct password + key, err := DecryptKey(keyjson, password) + if err != nil { + t.Fatalf("test %d: json key failed to decrypt: %v", i, err) + } + if key.Address != address { + t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address) + } + // Recrypt with a new password and start over + password += "new data appended" // nolint: gosec + if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil { + t.Errorf("test %d: failed to recrypt key %v", i, err) + } + } +} diff --git a/accounts/keystore/plain.go b/accounts/keystore/plain.go new file mode 100644 index 0000000..840ea88 --- /dev/null +++ b/accounts/keystore/plain.go @@ -0,0 +1,61 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/astranetworld/ast/common/types" +) + +type keyStorePlain struct { + keysDirPath string +} + +func (ks keyStorePlain) GetKey(addr types.Address, filename, auth string) (*Key, error) { + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + key := new(Key) + if err := json.NewDecoder(fd).Decode(key); err != nil { + return nil, err + } + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr) + } + return key, nil +} + +func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error { + content, err := json.Marshal(key) + if err != nil { + return err + } + return writeKeyFile(filename, content) +} + +func (ks keyStorePlain) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go new file mode 100644 index 0000000..02e16c5 --- /dev/null +++ b/accounts/keystore/plain_test.go @@ -0,0 +1,257 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package keystore + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/avm/common" +) + +func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { + d := t.TempDir() + if encrypted { + ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true} + } else { + ks = &keyStorePlain{d} + } + return d, ks +} + +func TestKeyStorePlain(t *testing.T) { + _, ks := tmpKeyStoreIface(t, false) + + pass := "" // not used but required by API + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphrase(t *testing.T) { + _, ks := tmpKeyStoreIface(t, true) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphraseDecryptionFail(t *testing.T) { + _, ks := tmpKeyStoreIface(t, true) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt { + t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt) + } +} + +func TestImportPreSaleKey(t *testing.T) { + dir, ks := tmpKeyStoreIface(t, true) + + // file content of a presale key file generated with: + // python pyethsaletool.py genwallet + // with password "foo" + fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}" + pass := "foo" + account, _, err := importPreSaleKey(ks, []byte(fileContent), pass) + if err != nil { + t.Fatal(err) + } + if account.Address != types.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") { + t.Errorf("imported account has wrong address %x", account.Address) + } + if !strings.HasPrefix(account.URL.Path, dir) { + t.Errorf("imported account file not in keystore directory: %q", account.URL) + } +} + +// Test and utils for the key store tests in the Ethereum JSON tests; +// testdataKeyStoreTests/basic_tests.json +type KeyStoreTestV3 struct { + Json encryptedKeyJSONV3 + Password string + Priv string +} + +type KeyStoreTestV1 struct { + Json encryptedKeyJSONV1 + Password string + Priv string +} + +func TestV3_PBKDF2_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t) +} + +var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests") + +func skipIfSubmoduleMissing(t *testing.T) { + if !common.FileExist(testsSubmodule) { + t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule) + } +} + +func TestV3_PBKDF2_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test1"], t) +} + +func TestV3_PBKDF2_3(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["python_generated_test_with_odd_iv"], t) +} + +func TestV3_PBKDF2_4(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["evilnonce"], t) +} + +func TestV3_Scrypt_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_scrypt"], t) +} + +func TestV3_Scrypt_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test2"], t) +} + +func TestV1_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t) + testDecryptV1(tests["test1"], t) +} + +func TestV1_2(t *testing.T) { + t.Parallel() + ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true} + addr := types.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e") + file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e" + k, err := ks.GetKey(addr, file, "g") + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)) + expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" + if privHex != expectedHex { + t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex)) + } +} + +func testDecryptV3(test KeyStoreTestV3, t *testing.T) { + privBytes, _, err := decryptKeyV3(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func testDecryptV1(test KeyStoreTestV1, t *testing.T) { + privBytes, _, err := decryptKeyV1(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 { + tests := make(map[string]KeyStoreTestV3) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 { + tests := make(map[string]KeyStoreTestV1) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func TestKeyForDirectICAP(t *testing.T) { + t.Parallel() + key := NewKeyForDirectICAP(rand.Reader) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex()) + } +} + +func TestV3_31_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["31_byte_key"], t) +} + +func TestV3_30_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["30_byte_key"], t) +} diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go new file mode 100644 index 0000000..a8d7234 --- /dev/null +++ b/accounts/keystore/presale.go @@ -0,0 +1,150 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common/crypto" + "github.com/google/uuid" + "golang.org/x/crypto/pbkdf2" +) + +// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON +func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { + key, err := decryptPreSaleKey(keyJSON, password) + if err != nil { + return accounts.Account{}, nil, err + } + key.Id, err = uuid.NewRandom() + if err != nil { + return accounts.Account{}, nil, err + } + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{ + Scheme: KeyStoreScheme, + Path: keyStore.JoinPath(keyFileName(key.Address)), + }, + } + err = keyStore.StoreKey(a.URL.Path, key, password) + return a, key, err +} + +func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) { + preSaleKeyStruct := struct { + EncSeed string + EthAddr string + Email string + BtcAddr string + }{} + err = json.Unmarshal(fileContent, &preSaleKeyStruct) + if err != nil { + return nil, err + } + encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) + if err != nil { + return nil, errors.New("invalid hex in encSeed") + } + if len(encSeedBytes) < 16 { + return nil, errors.New("invalid encSeed, too short") + } + iv := encSeedBytes[:16] + cipherText := encSeedBytes[16:] + /* + See https://github.com/ethereum/pyethsaletool + + pyethsaletool generates the encryption key from password by + 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:(). + 16 byte key length within PBKDF2 and resulting key is used as AES key + */ + passBytes := []byte(password) + derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) + plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) + if err != nil { + return nil, err + } + ethPriv := crypto.Keccak256(plainText) + ecKey := crypto.ToECDSAUnsafe(ethPriv) + + key = &Key{ + Id: uuid.UUID{}, + Address: crypto.PubkeyToAddress(ecKey.PublicKey), + PrivateKey: ecKey, + } + derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" + expectedAddr := preSaleKeyStruct.EthAddr + if derivedAddr != expectedAddr { + err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) + } + return key, err +} + +func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { + // AES-128 is selected due to size of encryptKey. + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + stream := cipher.NewCTR(aesBlock, iv) + outText := make([]byte, len(inText)) + stream.XORKeyStream(outText, inText) + return outText, err +} + +func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + decrypter := cipher.NewCBCDecrypter(aesBlock, iv) + paddedPlaintext := make([]byte, len(cipherText)) + decrypter.CryptBlocks(paddedPlaintext, cipherText) + plaintext := pkcs7Unpad(paddedPlaintext) + if plaintext == nil { + return nil, ErrDecrypt + } + return plaintext, err +} + +// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes +func pkcs7Unpad(in []byte) []byte { + if len(in) == 0 { + return nil + } + + padding := in[len(in)-1] + if int(padding) > len(in) || padding > aes.BlockSize { + return nil + } else if padding == 0 { + return nil + } + + for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { + if in[i] != padding { + return nil + } + } + return in[:len(in)-int(padding)] +} diff --git a/accounts/keystore/testdata/dupes/1 b/accounts/keystore/testdata/dupes/1 new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/dupes/1 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/2 b/accounts/keystore/testdata/dupes/2 new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/dupes/2 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/foo b/accounts/keystore/testdata/dupes/foo new file mode 100644 index 0000000..c57060a --- /dev/null +++ b/accounts/keystore/testdata/dupes/foo @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/.hiddenfile b/accounts/keystore/testdata/keystore/.hiddenfile new file mode 100644 index 0000000..d91facc --- /dev/null +++ b/accounts/keystore/testdata/keystore/.hiddenfile @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} diff --git a/accounts/keystore/testdata/keystore/README b/accounts/keystore/testdata/keystore/README new file mode 100644 index 0000000..6af9ac3 --- /dev/null +++ b/accounts/keystore/testdata/keystore/README @@ -0,0 +1,21 @@ +This directory contains accounts for testing. +The password that unlocks them is "foobar". + +The "good" key files which are supposed to be loadable are: + +- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 + Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8 +- File: aaa + Address: 0xf466859ead1932d743d622cb74fc058882e8648a +- File: zzz + Address: 0x289d485d9771714cce91d3393d764e1311907acc + +The other files (including this README) are broken in various ways +and should not be picked up by package accounts: + +- File: no-address (missing address field, otherwise same as "aaa") +- File: garbage (file with random data) +- File: empty (file with no content) +- File: swapfile~ (should be skipped) +- File: .hiddenfile (should be skipped) +- File: foo/... (should be skipped because it is a directory) diff --git a/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 new file mode 100644 index 0000000..c57060a --- /dev/null +++ b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/aaa b/accounts/keystore/testdata/keystore/aaa new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/keystore/aaa @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/empty b/accounts/keystore/testdata/keystore/empty new file mode 100644 index 0000000..e69de29 diff --git a/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e new file mode 100644 index 0000000..309841e --- /dev/null +++ b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e @@ -0,0 +1 @@ +{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/garbage b/accounts/keystore/testdata/keystore/garbage new file mode 100644 index 0000000..ff45091 Binary files /dev/null and b/accounts/keystore/testdata/keystore/garbage differ diff --git a/accounts/keystore/testdata/keystore/no-address b/accounts/keystore/testdata/keystore/no-address new file mode 100644 index 0000000..ad51269 --- /dev/null +++ b/accounts/keystore/testdata/keystore/no-address @@ -0,0 +1 @@ +{"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/zero b/accounts/keystore/testdata/keystore/zero new file mode 100644 index 0000000..b52617f --- /dev/null +++ b/accounts/keystore/testdata/keystore/zero @@ -0,0 +1 @@ +{"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/zzz b/accounts/keystore/testdata/keystore/zzz new file mode 100644 index 0000000..cfd8a47 --- /dev/null +++ b/accounts/keystore/testdata/keystore/zzz @@ -0,0 +1 @@ +{"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e b/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e new file mode 100644 index 0000000..498d813 --- /dev/null +++ b/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e @@ -0,0 +1 @@ +{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"} \ No newline at end of file diff --git a/accounts/keystore/testdata/v1_test_vector.json b/accounts/keystore/testdata/v1_test_vector.json new file mode 100644 index 0000000..3d09b55 --- /dev/null +++ b/accounts/keystore/testdata/v1_test_vector.json @@ -0,0 +1,28 @@ +{ + "test1": { + "json": { + "Crypto": { + "cipher": "aes-128-cbc", + "cipherparams": { + "iv": "35337770fc2117994ecdcad026bccff4" + }, + "ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f" + }, + "mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644", + "version": "1" + }, + "address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e", + "id": "e25f7c1f-d318-4f29-b62c-687190d4d299", + "version": "1" + }, + "password": "g", + "priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" + } +} diff --git a/accounts/keystore/testdata/v3_test_vector.json b/accounts/keystore/testdata/v3_test_vector.json new file mode 100644 index 0000000..1e7f790 --- /dev/null +++ b/accounts/keystore/testdata/v3_test_vector.json @@ -0,0 +1,97 @@ +{ + "wikipage_test_vector_scrypt": { + "json": { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + }, + "password": "testpassword", + "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" + }, + "wikipage_test_vector_pbkdf2": { + "json": { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "6087dab2f9fdbbfaddc31a909735c1e6" + }, + "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", + "kdf" : "pbkdf2", + "kdfparams" : { + "c" : 262144, + "dklen" : 32, + "prf" : "hmac-sha256", + "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" + }, + "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + }, + "password": "testpassword", + "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" + }, + "31_byte_key": { + "json": { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "e0c41130a323adc1446fc82f724bca2f" + }, + "ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 2, + "r" : 8, + "p" : 1, + "salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63" + }, + "mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5" + }, + "id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c", + "version" : 3 + }, + "password": "foo", + "priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35" + }, + "30_byte_key": { + "json": { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "3ca92af36ad7c2cd92454c59cea5ef00" + }, + "ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 2, + "r" : 8, + "p" : 1, + "salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b" + }, + "mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420" + }, + "id" : "a37e1559-5955-450d-8075-7b8931b392b2", + "version" : 3 + }, + "password": "foo", + "priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018" + } +} diff --git a/accounts/keystore/testdata/very-light-scrypt.json b/accounts/keystore/testdata/very-light-scrypt.json new file mode 100644 index 0000000..d23b9b2 --- /dev/null +++ b/accounts/keystore/testdata/very-light-scrypt.json @@ -0,0 +1 @@ +{"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3} diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go new file mode 100644 index 0000000..b5bc5a6 --- /dev/null +++ b/accounts/keystore/wallet.go @@ -0,0 +1,150 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package keystore + +import ( + "math/big" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/common" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/transaction" +) + +// keystoreWallet implements the accounts.Wallet interface for the original +// keystore. +type keystoreWallet struct { + account accounts.Account // Single account contained in this wallet + keystore *KeyStore // Keystore where the account originates from +} + +// URL implements accounts.Wallet, returning the URL of the account within. +func (w *keystoreWallet) URL() accounts.URL { + return w.account.URL +} + +// Status implements accounts.Wallet, returning whether the account held by the +// keystore wallet is unlocked or not. +func (w *keystoreWallet) Status() (string, error) { + w.keystore.mu.RLock() + defer w.keystore.mu.RUnlock() + + if _, ok := w.keystore.unlocked[w.account.Address]; ok { + return "Unlocked", nil + } + return "Locked", nil +} + +// Open implements accounts.Wallet, but is a noop for plain wallets since there +// is no connection or decryption step necessary to access the list of accounts. +func (w *keystoreWallet) Open(passphrase string) error { return nil } + +// Close implements accounts.Wallet, but is a noop for plain wallets since there +// is no meaningful open operation. +func (w *keystoreWallet) Close() error { return nil } + +// Accounts implements accounts.Wallet, returning an account list consisting of +// a single account that the plain keystore wallet contains. +func (w *keystoreWallet) Accounts() []accounts.Account { + return []accounts.Account{w.account} +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not wrapped by this wallet instance. +func (w *keystoreWallet) Contains(account accounts.Account) bool { + return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL) +} + +// Derive implements accounts.Wallet, but is a noop for plain wallets since there +// is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, accounts.ErrNotSupported +} + +// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since +// there is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain common.ChainStateReader) { +} + +// signHash attempts to sign the given hash with +// the given account. If the wallet does not wrap this particular account, an +// error is returned to avoid account leakage (even though in theory we may be +// able to sign via our shared keystore backend). +func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHash(account, hash) +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed. +func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed. +func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +// SignText implements accounts.Wallet, attempting to sign the hash of +// the given text with the given account. +func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the +// hash of the given text with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) +} + +// SignTx implements accounts.Wallet, attempting to sign the given transaction +// with the given account. If the wallet does not wrap this particular account, +// an error is returned to avoid account leakage (even though in theory we may +// be able to sign via our shared keystore backend). +func (w *keystoreWallet) SignTx(account accounts.Account, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTx(account, tx, chainID) +} + +// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given +// transaction with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *transaction.Transaction, chainID *big.Int) (*transaction.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID) +} diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go new file mode 100644 index 0000000..b4116d6 --- /dev/null +++ b/accounts/keystore/watch.go @@ -0,0 +1,131 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris +// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris + +package keystore + +import ( + "github.com/fsnotify/fsnotify" + "time" + + "github.com/astranetworld/ast/log" +) + +type watcher struct { + ac *accountCache + running bool // set to true when runloop begins + runEnded bool // set to true when runloop ends + starting bool // set to true prior to runloop starting + quit chan struct{} +} + +func newWatcher(ac *accountCache) *watcher { + return &watcher{ + ac: ac, + quit: make(chan struct{}), + } +} + +// enabled returns false on systems not supported. +func (*watcher) enabled() bool { return true } + +// starts the watcher loop in the background. +// Start a watcher in the background if that's not already in progress. +// The caller must hold w.ac.mu. +func (w *watcher) start() { + if w.starting || w.running { + return + } + w.starting = true + go w.loop() +} + +func (w *watcher) close() { + close(w.quit) +} + +func (w *watcher) loop() { + defer func() { + w.ac.mu.Lock() + w.running = false + w.starting = false + w.runEnded = true + w.ac.mu.Unlock() + }() + logger := log.New("path", w.ac.keydir) + + // Create new watcher. + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error("Failed to start filesystem watcher", "err", err) + return + } + defer watcher.Close() + if err := watcher.Add(w.ac.keydir); err != nil { + logger.Warn("Failed to watch keystore folder", "err", err) + return + } + + logger.Trace("Started watching keystore folder", "folder", w.ac.keydir) + defer logger.Trace("Stopped watching keystore folder") + + w.ac.mu.Lock() + w.running = true + w.ac.mu.Unlock() + + // Wait for file system events and reload. + // When an event occurs, the reload call is delayed a bit so that + // multiple events arriving quickly only cause a single reload. + var ( + debounceDuration = 500 * time.Millisecond + rescanTriggered = false + debounce = time.NewTimer(0) + ) + // Ignore initial trigger + if !debounce.Stop() { + <-debounce.C + } + defer debounce.Stop() + for { + select { + case <-w.quit: + return + case _, ok := <-watcher.Events: + if !ok { + return + } + // Trigger the scan (with delay), if not already triggered + if !rescanTriggered { + debounce.Reset(debounceDuration) + rescanTriggered = true + } + // The fsnotify library does provide more granular event-info, it + // would be possible to refresh individual affected files instead + // of scheduling a full rescan. For most cases though, the + // full rescan is quick and obviously simplest. + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Info("Filsystem watcher error", "err", err) + case <-debounce.C: + w.ac.scanAccounts() + rescanTriggered = false + } + } +} diff --git a/accounts/keystore/watch_fallback.go b/accounts/keystore/watch_fallback.go new file mode 100644 index 0000000..ecd38da --- /dev/null +++ b/accounts/keystore/watch_fallback.go @@ -0,0 +1,35 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris) +// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris + +// This is the fallback implementation of directory watching. +// It is used on unsupported platforms. + +package keystore + +type watcher struct { + running bool + runEnded bool +} + +func newWatcher(*accountCache) *watcher { return new(watcher) } +func (*watcher) start() {} +func (*watcher) close() {} + +// enabled returns false on systems not supported. +func (*watcher) enabled() bool { return false } diff --git a/accounts/manager.go b/accounts/manager.go new file mode 100644 index 0000000..c07f24f --- /dev/null +++ b/accounts/manager.go @@ -0,0 +1,272 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "reflect" + "sort" + "sync" + + "github.com/astranetworld/ast/common/types" + event "github.com/astranetworld/ast/modules/event/v2" +) + +// managerSubBufferSize determines how many incoming wallet events +// the manager will buffer in its channel. +const managerSubBufferSize = 50 + +// Config contains the settings of the global account manager. +// +// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management +// is removed in favor of Clef. +type Config struct { + InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed +} + +// newBackendEvent lets the manager know it should +// track the given backend for wallet updates. +type newBackendEvent struct { + backend Backend + processed chan struct{} // Informs event emitter that backend has been integrated +} + +// Manager is an overarching account manager that can communicate with various +// backends for signing transactions. +type Manager struct { + config *Config // Global account manager configurations + backends map[reflect.Type][]Backend // Index of backends currently registered + updaters []event.Subscription // Wallet update subscriptions for all backends + updates chan WalletEvent // Subscription sink for backend wallet changes + newBackends chan newBackendEvent // Incoming backends to be tracked by the manager + wallets []Wallet // Cache of all wallets from all registered backends + + quit chan chan error + term chan struct{} // Channel is closed upon termination of the update loop + lock sync.RWMutex +} + +// NewManager creates a generic account manager to sign transaction via various +// supported backends. +func NewManager(config *Config, backends ...Backend) *Manager { + // Retrieve the initial list of wallets from the backends and sort by URL + var wallets []Wallet + for _, backend := range backends { + wallets = merge(wallets, backend.Wallets()...) + } + // Subscribe to wallet notifications from all backends + updates := make(chan WalletEvent, managerSubBufferSize) + + subs := make([]event.Subscription, len(backends)) + for i, backend := range backends { + subs[i] = backend.Subscribe(updates) + } + // Assemble the account manager and return + am := &Manager{ + config: config, + backends: make(map[reflect.Type][]Backend), + updaters: subs, + updates: updates, + newBackends: make(chan newBackendEvent), + wallets: wallets, + quit: make(chan chan error), + term: make(chan struct{}), + } + for _, backend := range backends { + kind := reflect.TypeOf(backend) + am.backends[kind] = append(am.backends[kind], backend) + } + go am.update() + + return am +} + +// Close terminates the account manager's internal notification processes. +func (am *Manager) Close() error { + errc := make(chan error) + am.quit <- errc + return <-errc +} + +// Config returns the configuration of account manager. +func (am *Manager) Config() *Config { + return am.config +} + +// AddBackend starts the tracking of an additional backend for wallet updates. +// cmd/geth assumes once this func returns the backends have been already integrated. +func (am *Manager) AddBackend(backend Backend) { + done := make(chan struct{}) + am.newBackends <- newBackendEvent{backend, done} + <-done +} + +// update is the wallet event loop listening for notifications from the backends +// and updating the cache of wallets. +func (am *Manager) update() { + // Close all subscriptions when the manager terminates + defer func() { + am.lock.Lock() + for _, sub := range am.updaters { + sub.Unsubscribe() + } + am.updaters = nil + am.lock.Unlock() + }() + + // Loop until termination + for { + select { + case walletEvent := <-am.updates: + // Wallet event arrived, update local cache + am.lock.Lock() + switch walletEvent.Kind { + case WalletArrived: + am.wallets = merge(am.wallets, walletEvent.Wallet) + case WalletDropped: + am.wallets = drop(am.wallets, walletEvent.Wallet) + } + am.lock.Unlock() + + // Notify any listeners of the event + + //am.feed.Send(&walletEvent) + event.GlobalEvent.Send(walletEvent) + case backendEvent := <-am.newBackends: + am.lock.Lock() + // Update caches + backend := backendEvent.backend + am.wallets = merge(am.wallets, backend.Wallets()...) + am.updaters = append(am.updaters, backend.Subscribe(am.updates)) + kind := reflect.TypeOf(backend) + am.backends[kind] = append(am.backends[kind], backend) + am.lock.Unlock() + close(backendEvent.processed) + case errc := <-am.quit: + // Manager terminating, return + errc <- nil + // Signals event emitters the loop is not receiving values + // to prevent them from getting stuck. + close(am.term) + return + } + } +} + +// Backends retrieves the backend(s) with the given type from the account manager. +func (am *Manager) Backends(kind reflect.Type) []Backend { + am.lock.RLock() + defer am.lock.RUnlock() + + return am.backends[kind] +} + +// Wallets returns all signer accounts registered under this account manager. +func (am *Manager) Wallets() []Wallet { + am.lock.RLock() + defer am.lock.RUnlock() + + return am.walletsNoLock() +} + +// walletsNoLock returns all registered wallets. Callers must hold am.lock. +func (am *Manager) walletsNoLock() []Wallet { + cpy := make([]Wallet, len(am.wallets)) + copy(cpy, am.wallets) + return cpy +} + +// Wallet retrieves the wallet associated with a particular URL. +func (am *Manager) Wallet(url string) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + parsed, err := parseURL(url) + if err != nil { + return nil, err + } + for _, wallet := range am.walletsNoLock() { + if wallet.URL() == parsed { + return wallet, nil + } + } + return nil, ErrUnknownWallet +} + +// Accounts returns all account addresses of all wallets within the account manager +func (am *Manager) Accounts() []types.Address { + am.lock.RLock() + defer am.lock.RUnlock() + + addresses := make([]types.Address, 0) // return [] instead of nil if empty + for _, wallet := range am.wallets { + for _, account := range wallet.Accounts() { + addresses = append(addresses, account.Address) + } + } + return addresses +} + +// Find attempts to locate the wallet corresponding to a specific account. Since +// accounts can be dynamically added to and removed from wallets, this method has +// a linear runtime in the number of wallets. +func (am *Manager) Find(account Account) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + for _, wallet := range am.wallets { + if wallet.Contains(account) { + return wallet, nil + } + } + return nil, ErrUnknownAccount +} + +// Subscribe creates an async subscription to receive notifications when the +// manager detects the arrival or departure of a wallet from any of its backends. +func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { + return event.GlobalEvent.Subscribe(sink) +} + +// merge is a sorted analogue of append for wallets, where the ordering of the +// origin list is preserved by inserting new wallets at the correct position. +// +// The original slice is assumed to be already sorted by URL. +func merge(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + slice = append(slice, wallet) + continue + } + slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) + } + return slice +} + +// drop is the couterpart of merge, which looks up wallets from within the sorted +// cache and removes the ones specified. +func drop(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + // Wallet not found, may happen during startup + continue + } + slice = append(slice[:n], slice[n+1:]...) + } + return slice +} diff --git a/accounts/sort.go b/accounts/sort.go new file mode 100644 index 0000000..a3ec6d1 --- /dev/null +++ b/accounts/sort.go @@ -0,0 +1,31 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +// AccountsByURL implements sort.Interface for []Account based on the URL field. +type AccountsByURL []Account + +func (a AccountsByURL) Len() int { return len(a) } +func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 } + +// WalletsByURL implements sort.Interface for []Wallet based on the URL field. +type WalletsByURL []Wallet + +func (w WalletsByURL) Len() int { return len(w) } +func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] } +func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 } diff --git a/accounts/url.go b/accounts/url.go new file mode 100644 index 0000000..2350658 --- /dev/null +++ b/accounts/url.go @@ -0,0 +1,103 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// URL represents the canonical identification URL of a wallet or account. +// +// It is a simplified version of url.URL, with the important limitations (which +// are considered features here) that it contains value-copyable components only, +// as well as that it doesn't do any URL encoding/decoding of special characters. +// +// The former is important to allow an account to be copied without leaving live +// references to the original version, whereas the latter is important to ensure +// one single canonical form opposed to many allowed ones by the RFC 3986 spec. +// +// As such, these URLs should not be used outside of the scope of an Ethereum +// wallet or account. +type URL struct { + Scheme string // Protocol scheme to identify a capable account backend + Path string // Path for the backend to identify a unique entity +} + +// parseURL converts a user supplied URL into the accounts specific structure. +func parseURL(url string) (URL, error) { + parts := strings.Split(url, "://") + if len(parts) != 2 || parts[0] == "" { + return URL{}, errors.New("protocol scheme missing") + } + return URL{ + Scheme: parts[0], + Path: parts[1], + }, nil +} + +// String implements the stringer interface. +func (u URL) String() string { + if u.Scheme != "" { + return fmt.Sprintf("%s://%s", u.Scheme, u.Path) + } + return u.Path +} + +// TerminalString implements the log.TerminalStringer interface. +func (u URL) TerminalString() string { + url := u.String() + if len(url) > 32 { + return url[:31] + ".." + } + return url +} + +// MarshalJSON implements the json.Marshaller interface. +func (u URL) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +// UnmarshalJSON parses url. +func (u *URL) UnmarshalJSON(input []byte) error { + var textURL string + err := json.Unmarshal(input, &textURL) + if err != nil { + return err + } + url, err := parseURL(textURL) + if err != nil { + return err + } + u.Scheme = url.Scheme + u.Path = url.Path + return nil +} + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +func (u URL) Cmp(url URL) int { + if u.Scheme == url.Scheme { + return strings.Compare(u.Path, url.Path) + } + return strings.Compare(u.Scheme, url.Scheme) +} diff --git a/accounts/url_test.go b/accounts/url_test.go new file mode 100644 index 0000000..4822043 --- /dev/null +++ b/accounts/url_test.go @@ -0,0 +1,97 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package accounts + +import ( + "testing" +) + +func TestURLParsing(t *testing.T) { + url, err := parseURL("https://ethereum.org") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path) + } + + for _, u := range []string{"ethereum.org", ""} { + if _, err = parseURL(u); err == nil { + t.Errorf("input %v, expected err, got: nil", u) + } + } +} + +func TestURLString(t *testing.T) { + url := URL{Scheme: "https", Path: "ethereum.org"} + if url.String() != "https://ethereum.org" { + t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String()) + } + + url = URL{Scheme: "", Path: "ethereum.org"} + if url.String() != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.String()) + } +} + +func TestURLMarshalJSON(t *testing.T) { + url := URL{Scheme: "https", Path: "ethereum.org"} + json, err := url.MarshalJSON() + if err != nil { + t.Errorf("unexpcted error: %v", err) + } + if string(json) != "\"https://ethereum.org\"" { + t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json)) + } +} + +func TestURLUnmarshalJSON(t *testing.T) { + url := &URL{} + err := url.UnmarshalJSON([]byte("\"https://ethereum.org\"")) + if err != nil { + t.Errorf("unexpcted error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "https", url.Path) + } +} + +func TestURLComparison(t *testing.T) { + tests := []struct { + urlA URL + urlB URL + expect int + }{ + {URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0}, + {URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1}, + {URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1}, + {URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1}, + } + + for i, tt := range tests { + result := tt.urlA.Cmp(tt.urlB) + if result != tt.expect { + t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result) + } + } +} diff --git a/api/protocol/consensus_proto/consensus.pb.go b/api/protocol/consensus_proto/consensus.pb.go new file mode 100644 index 0000000..ce759aa --- /dev/null +++ b/api/protocol/consensus_proto/consensus.pb.go @@ -0,0 +1,383 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.20.0 +// source: consensus.proto + +package consensus_proto + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PBSigners struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signer []*PBSigner `protobuf:"bytes,1,rep,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *PBSigners) Reset() { + *x = PBSigners{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PBSigners) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PBSigners) ProtoMessage() {} + +func (x *PBSigners) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PBSigners.ProtoReflect.Descriptor instead. +func (*PBSigners) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{0} +} + +func (x *PBSigners) GetSigner() []*PBSigner { + if x != nil { + return x.Signer + } + return nil +} + +type PBSigner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Public string `protobuf:"bytes,1,opt,name=public,proto3" json:"public,omitempty"` + Address *types_pb.H160 `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *PBSigner) Reset() { + *x = PBSigner{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PBSigner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PBSigner) ProtoMessage() {} + +func (x *PBSigner) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PBSigner.ProtoReflect.Descriptor instead. +func (*PBSigner) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{1} +} + +func (x *PBSigner) GetPublic() string { + if x != nil { + return x.Public + } + return "" +} + +func (x *PBSigner) GetAddress() *types_pb.H160 { + if x != nil { + return x.Address + } + return nil +} + +func (x *PBSigner) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type PBVote struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PBVote) Reset() { + *x = PBVote{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PBVote) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PBVote) ProtoMessage() {} + +func (x *PBVote) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PBVote.ProtoReflect.Descriptor instead. +func (*PBVote) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{2} +} + +type PBPoaInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Public string `protobuf:"bytes,1,opt,name=public,proto3" json:"public,omitempty"` + Sign []byte `protobuf:"bytes,2,opt,name=sign,proto3" json:"sign,omitempty"` + Type int64 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *PBPoaInfo) Reset() { + *x = PBPoaInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PBPoaInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PBPoaInfo) ProtoMessage() {} + +func (x *PBPoaInfo) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PBPoaInfo.ProtoReflect.Descriptor instead. +func (*PBPoaInfo) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{3} +} + +func (x *PBPoaInfo) GetPublic() string { + if x != nil { + return x.Public + } + return "" +} + +func (x *PBPoaInfo) GetSign() []byte { + if x != nil { + return x.Sign + } + return nil +} + +func (x *PBPoaInfo) GetType() int64 { + if x != nil { + return x.Type + } + return 0 +} + +var File_consensus_proto protoreflect.FileDescriptor + +var file_consensus_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x0c, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x5f, 0x70, 0x62, 0x1a, + 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3b, 0x0a, 0x09, 0x50, 0x42, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x50, 0x42, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x22, 0x66, 0x0a, 0x08, 0x50, 0x42, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, 0x30, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x08, 0x0a, 0x06, 0x50, 0x42, + 0x56, 0x6f, 0x74, 0x65, 0x22, 0x4b, 0x0a, 0x09, 0x50, 0x42, 0x50, 0x6f, 0x61, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x67, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, 0x6d, 0x63, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x73, + 0x65, 0x6e, 0x73, 0x75, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_consensus_proto_rawDescOnce sync.Once + file_consensus_proto_rawDescData = file_consensus_proto_rawDesc +) + +func file_consensus_proto_rawDescGZIP() []byte { + file_consensus_proto_rawDescOnce.Do(func() { + file_consensus_proto_rawDescData = protoimpl.X.CompressGZIP(file_consensus_proto_rawDescData) + }) + return file_consensus_proto_rawDescData +} + +var file_consensus_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_consensus_proto_goTypes = []interface{}{ + (*PBSigners)(nil), // 0: consensus_pb.PBSigners + (*PBSigner)(nil), // 1: consensus_pb.PBSigner + (*PBVote)(nil), // 2: consensus_pb.PBVote + (*PBPoaInfo)(nil), // 3: consensus_pb.PBPoaInfo + (*types_pb.H160)(nil), // 4: types_pb.H160 +} +var file_consensus_proto_depIdxs = []int32{ + 1, // 0: consensus_pb.PBSigners.signer:type_name -> consensus_pb.PBSigner + 4, // 1: consensus_pb.PBSigner.address:type_name -> types_pb.H160 + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_consensus_proto_init() } +func file_consensus_proto_init() { + if File_consensus_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_consensus_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PBSigners); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_consensus_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PBSigner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_consensus_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PBVote); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_consensus_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PBPoaInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_consensus_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_consensus_proto_goTypes, + DependencyIndexes: file_consensus_proto_depIdxs, + MessageInfos: file_consensus_proto_msgTypes, + }.Build() + File_consensus_proto = out.File + file_consensus_proto_rawDesc = nil + file_consensus_proto_goTypes = nil + file_consensus_proto_depIdxs = nil +} diff --git a/api/protocol/consensus_proto/consensus.proto b/api/protocol/consensus_proto/consensus.proto new file mode 100644 index 0000000..4ebc990 --- /dev/null +++ b/api/protocol/consensus_proto/consensus.proto @@ -0,0 +1,41 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package consensus_pb; + +import "types_pb/types.proto"; +option go_package = "github.com/astranetworld/ast/api/protocol/consensus_proto"; + +message PBSigners { + repeated PBSigner signer = 1; +} + +message PBSigner { + string public = 1; + types_pb.H160 address = 2; + string version = 3; +} + +message PBVote { + +} + +message PBPoaInfo { + string public = 1; + bytes sign = 2; + int64 type = 3; +} diff --git a/api/protocol/consensus_proto/gen.go b/api/protocol/consensus_proto/gen.go new file mode 100644 index 0000000..4143321 --- /dev/null +++ b/api/protocol/consensus_proto/gen.go @@ -0,0 +1,19 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package consensus_proto + +//go:generate protoc -I=../ -I=. -I=../include --go_out=paths=source_relative:. consensus.proto diff --git a/api/protocol/ext/gen.go b/api/protocol/ext/gen.go new file mode 100644 index 0000000..4c10c29 --- /dev/null +++ b/api/protocol/ext/gen.go @@ -0,0 +1,19 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package ext + +//go:generate protoc -I=../ -I=. -I=../include --go_out=paths=source_relative:. options.proto diff --git a/api/protocol/ext/options.pb.go b/api/protocol/ext/options.pb.go new file mode 100644 index 0000000..3bd9f7e --- /dev/null +++ b/api/protocol/ext/options.pb.go @@ -0,0 +1,151 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.20.0 +// source: options.proto + +package ext + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var file_options_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50000, + Name: "ext.cast_type", + Tag: "bytes,50000,opt,name=cast_type", + Filename: "options.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50001, + Name: "ext.ssz_size", + Tag: "bytes,50001,opt,name=ssz_size", + Filename: "options.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50002, + Name: "ext.ssz_max", + Tag: "bytes,50002,opt,name=ssz_max", + Filename: "options.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50003, + Name: "ext.spec_name", + Tag: "bytes,50003,opt,name=spec_name", + Filename: "options.proto", + }, +} + +// Extension fields to descriptorpb.FieldOptions. +var ( + // optional string cast_type = 50000; + E_CastType = &file_options_proto_extTypes[0] + // optional string ssz_size = 50001; + E_SszSize = &file_options_proto_extTypes[1] + // optional string ssz_max = 50002; + E_SszMax = &file_options_proto_extTypes[2] + // optional string spec_name = 50003; + E_SpecName = &file_options_proto_extTypes[3] +) + +var File_options_proto protoreflect.FileDescriptor + +var file_options_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x03, 0x65, 0x78, 0x74, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x3c, 0x0a, 0x09, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x73, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x3a, 0x3a, 0x0a, 0x08, 0x73, 0x73, 0x7a, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0xd1, 0x86, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x73, 0x7a, 0x53, 0x69, 0x7a, 0x65, + 0x3a, 0x38, 0x0a, 0x07, 0x73, 0x73, 0x7a, 0x5f, 0x6d, 0x61, 0x78, 0x12, 0x1d, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd2, 0x86, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x73, 0x7a, 0x4d, 0x61, 0x78, 0x3a, 0x3c, 0x0a, 0x09, 0x73, 0x70, + 0x65, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x86, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x73, 0x70, 0x65, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2f, 0x61, 0x6d, 0x63, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x2f, 0x65, 0x78, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_options_proto_goTypes = []interface{}{ + (*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions +} +var file_options_proto_depIdxs = []int32{ + 0, // 0: ext.cast_type:extendee -> google.protobuf.FieldOptions + 0, // 1: ext.ssz_size:extendee -> google.protobuf.FieldOptions + 0, // 2: ext.ssz_max:extendee -> google.protobuf.FieldOptions + 0, // 3: ext.spec_name:extendee -> google.protobuf.FieldOptions + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 0, // [0:4] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_options_proto_init() } +func file_options_proto_init() { + if File_options_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_options_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 4, + NumServices: 0, + }, + GoTypes: file_options_proto_goTypes, + DependencyIndexes: file_options_proto_depIdxs, + ExtensionInfos: file_options_proto_extTypes, + }.Build() + File_options_proto = out.File + file_options_proto_rawDesc = nil + file_options_proto_goTypes = nil + file_options_proto_depIdxs = nil +} diff --git a/api/protocol/ext/options.proto b/api/protocol/ext/options.proto new file mode 100644 index 0000000..9319e70 --- /dev/null +++ b/api/protocol/ext/options.proto @@ -0,0 +1,29 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +syntax = "proto3"; + +package ext; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/astranetworld/ast/api/protocol/ext"; + +extend google.protobuf.FieldOptions { + string cast_type = 50000; + string ssz_size = 50001; + string ssz_max = 50002; + string spec_name = 50003; +} \ No newline at end of file diff --git a/api/protocol/include/google/api/annotations.proto b/api/protocol/include/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/api/protocol/include/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/api/protocol/include/google/api/http.proto b/api/protocol/include/google/api/http.proto new file mode 100644 index 0000000..2bd3a19 --- /dev/null +++ b/api/protocol/include/google/api/http.proto @@ -0,0 +1,318 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parmeters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// `HttpRule` defines the mapping of an RPC method to one or more HTTP +// REST API methods. The mapping specifies how different portions of the RPC +// request message are mapped to URL path, URL query parameters, and +// HTTP request body. The mapping is typically specified as an +// `google.api.http` annotation on the RPC method, +// see "google/api/annotations.proto" for details. +// +// The mapping consists of a field specifying the path template and +// method kind. The path template can refer to fields in the request +// message, as in the example below which describes a REST GET +// operation on a resource collection of messages: +// +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}"; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // mapped to the URL +// SubMessage sub = 2; // `sub.subfield` is url-mapped +// } +// message Message { +// string text = 1; // content of the resource +// } +// +// The same http annotation can alternatively be expressed inside the +// `GRPC API Configuration` YAML file. +// +// http: +// rules: +// - selector: .Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// This definition enables an automatic, bidrectional mapping of HTTP +// JSON to RPC. Example: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456/foo` | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))` +// +// In general, not only fields but also field paths can be referenced +// from a path pattern. Fields mapped to the path pattern cannot be +// repeated and must have a primitive (non-message) type. +// +// Any fields in the request message which are not bound by the path +// pattern automatically become (optional) HTTP query +// parameters. Assume the following definition of the request message: +// +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http).get = "/v1/messages/{message_id}"; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // mapped to the URL +// int64 revision = 2; // becomes a parameter +// SubMessage sub = 3; // `sub.subfield` becomes a parameter +// } +// +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))` +// +// Note that fields which are mapped to HTTP parameters must have a +// primitive type or a repeated primitive type. Message types are not +// allowed. In the case of a repeated type, the parameter can be +// repeated in the URL, as in `...?param=A¶m=B`. +// +// For HTTP method kinds which allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// put: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | RPC +// -----|----- +// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// put: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | RPC +// -----|----- +// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice of +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// +// This enables the following two alternative HTTP JSON to RPC +// mappings: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")` +// +// # Rules for HTTP mapping +// +// The rules for mapping HTTP path, query parameters, and body fields +// to the request message are as follows: +// +// 1. The `body` field specifies either `*` or a field path, or is +// omitted. If omitted, it indicates there is no HTTP request body. +// 2. Leaf fields (recursive expansion of nested messages in the +// request) can be classified into three types: +// (a) Matched in the URL template. +// (b) Covered by body (if body is `*`, everything except (a) fields; +// else everything under the body field) +// (c) All other fields. +// 3. URL query parameters found in the HTTP request are mapped to (c) fields. +// 4. Any body sent with an HTTP request can contain only (b) fields. +// +// The syntax of the path template is as follows: +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single path segment. The syntax `**` matches zero +// or more path segments, which must be the last part of the path except the +// `Verb`. The syntax `LITERAL` matches literal text in the path. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path, all characters +// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the +// Discovery Document as `{var}`. +// +// If a variable contains one or more path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path, all +// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables +// show up in the Discovery Document as `{+var}`. +// +// NOTE: While the single segment variable matches the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 +// Simple String Expansion, the multi segment variable **does not** match +// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. +// +// NOTE: the field paths in variables and in the `body` must not refer to +// repeated fields or map fields. +message HttpRule { + // Selects methods to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Used for listing and getting information about resources. + string get = 2; + + // Used for updating a resource. + string put = 3; + + // Used for creating a resource. + string post = 4; + + // Used for deleting a resource. + string delete = 5; + + // Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP body, or + // `*` for mapping all fields not captured by the path pattern to the HTTP + // body. NOTE: the referred field must not be a repeated field and must be + // present at the top-level of request message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // body of response. Other response fields are ignored. When + // not set, the response message will be used as HTTP body of response. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/api/protocol/include/google/api/httpbody.proto b/api/protocol/include/google/api/httpbody.proto new file mode 100644 index 0000000..4428515 --- /dev/null +++ b/api/protocol/include/google/api/httpbody.proto @@ -0,0 +1,78 @@ +// Copyright 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; +option java_multiple_files = true; +option java_outer_classname = "HttpBodyProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Message that represents an arbitrary HTTP body. It should only be used for +// payload formats that can't be represented as JSON, such as raw binary or +// an HTML page. +// +// +// This message can be used both in streaming and non-streaming API methods in +// the request as well as the response. +// +// It can be used as a top-level request field, which is convenient if one +// wants to extract parameters from either the URL or HTTP template into the +// request fields and also want access to the raw HTTP body. +// +// Example: +// +// message GetResourceRequest { +// // A unique request id. +// string request_id = 1; +// +// // The raw HTTP body is bound to this field. +// google.api.HttpBody http_body = 2; +// } +// +// service ResourceService { +// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); +// rpc UpdateResource(google.api.HttpBody) returns +// (google.protobuf.Empty); +// } +// +// Example with streaming methods: +// +// service CaldavService { +// rpc GetCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// rpc UpdateCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// } +// +// Use of this type only changes how the request and response bodies are +// handled, all other features will continue to work unchanged. +message HttpBody { + // The HTTP Content-Type header value specifying the content type of the body. + string content_type = 1; + + // The HTTP request/response body as raw binary. + bytes data = 2; + + // Application specific response metadata. Must be set in the first response + // for streaming APIs. + repeated google.protobuf.Any extensions = 3; +} \ No newline at end of file diff --git a/api/protocol/include/google/protobuf/any.proto b/api/protocol/include/google/protobuf/any.proto new file mode 100644 index 0000000..6ed8a23 --- /dev/null +++ b/api/protocol/include/google/protobuf/any.proto @@ -0,0 +1,158 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/api/protocol/include/google/protobuf/api.proto b/api/protocol/include/google/protobuf/api.proto new file mode 100644 index 0000000..3d598fc --- /dev/null +++ b/api/protocol/include/google/protobuf/api.proto @@ -0,0 +1,208 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/protobuf/types/known/apipb"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +message Api { + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; +} + +// Method represents a method of an API interface. +message Method { + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + Syntax syntax = 7; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inheriting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v2`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/api/protocol/include/google/protobuf/compiler/plugin.proto b/api/protocol/include/google/protobuf/compiler/plugin.proto new file mode 100644 index 0000000..9242aac --- /dev/null +++ b/api/protocol/include/google/protobuf/compiler/plugin.proto @@ -0,0 +1,183 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// +// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to +// change. +// +// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is +// just a program that reads a CodeGeneratorRequest from stdin and writes a +// CodeGeneratorResponse to stdout. +// +// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead +// of dealing with the raw protocol defined here. +// +// A plugin executable needs only to be placed somewhere in the path. The +// plugin should be named "protoc-gen-$NAME", and will then be used when the +// flag "--${NAME}_out" is passed to protoc. + +syntax = "proto2"; + +package google.protobuf.compiler; +option java_package = "com.google.protobuf.compiler"; +option java_outer_classname = "PluginProtos"; + +option go_package = "google.golang.org/protobuf/types/pluginpb"; + +import "google/protobuf/descriptor.proto"; + +// The version number of protocol compiler. +message Version { + optional int32 major = 1; + optional int32 minor = 2; + optional int32 patch = 3; + // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should + // be empty for mainline stable releases. + optional string suffix = 4; +} + +// An encoded CodeGeneratorRequest is written to the plugin's stdin. +message CodeGeneratorRequest { + // The .proto files that were explicitly listed on the command-line. The + // code generator should generate code only for these files. Each file's + // descriptor will be included in proto_file, below. + repeated string file_to_generate = 1; + + // The generator parameter passed on the command-line. + optional string parameter = 2; + + // FileDescriptorProtos for all files in files_to_generate and everything + // they import. The files will appear in topological order, so each file + // appears before any file that imports it. + // + // protoc guarantees that all proto_files will be written after + // the fields above, even though this is not technically guaranteed by the + // protobuf wire format. This theoretically could allow a plugin to stream + // in the FileDescriptorProtos and handle them one by one rather than read + // the entire set into memory at once. However, as of this writing, this + // is not similarly optimized on protoc's end -- it will store all fields in + // memory at once before sending them to the plugin. + // + // Type names of fields and extensions in the FileDescriptorProto are always + // fully qualified. + repeated FileDescriptorProto proto_file = 15; + + // The version number of protocol compiler. + optional Version compiler_version = 3; + +} + +// The plugin writes an encoded CodeGeneratorResponse to stdout. +message CodeGeneratorResponse { + // Error message. If non-empty, code generation failed. The plugin process + // should exit with status code zero even if it reports an error in this way. + // + // This should be used to indicate errors in .proto files which prevent the + // code generator from generating correct code. Errors which indicate a + // problem in protoc itself -- such as the input CodeGeneratorRequest being + // unparseable -- should be reported by writing a message to stderr and + // exiting with a non-zero status code. + optional string error = 1; + + // A bitmask of supported features that the code generator supports. + // This is a bitwise "or" of values from the Feature enum. + optional uint64 supported_features = 2; + + // Sync with code_generator.h. + enum Feature { + FEATURE_NONE = 0; + FEATURE_PROTO3_OPTIONAL = 1; + } + + // Represents a single generated file. + message File { + // The file name, relative to the output directory. The name must not + // contain "." or ".." components and must be relative, not be absolute (so, + // the file cannot lie outside the output directory). "/" must be used as + // the path separator, not "\". + // + // If the name is omitted, the content will be appended to the previous + // file. This allows the generator to break large files into small chunks, + // and allows the generated text to be streamed back to protoc so that large + // files need not reside completely in memory at one time. Note that as of + // this writing protoc does not optimize for this -- it will read the entire + // CodeGeneratorResponse before writing files to disk. + optional string name = 1; + + // If non-empty, indicates that the named file should already exist, and the + // content here is to be inserted into that file at a defined insertion + // point. This feature allows a code generator to extend the output + // produced by another code generator. The original generator may provide + // insertion points by placing special annotations in the file that look + // like: + // @@protoc_insertion_point(NAME) + // The annotation can have arbitrary text before and after it on the line, + // which allows it to be placed in a comment. NAME should be replaced with + // an identifier naming the point -- this is what other generators will use + // as the insertion_point. Code inserted at this point will be placed + // immediately above the line containing the insertion point (thus multiple + // insertions to the same point will come out in the order they were added). + // The double-@ is intended to make it unlikely that the generated code + // could contain things that look like insertion points by accident. + // + // For example, the C++ code generator places the following line in the + // .pb.h files that it generates: + // // @@protoc_insertion_point(namespace_scope) + // This line appears within the scope of the file's package namespace, but + // outside of any particular class. Another plugin can then specify the + // insertion_point "namespace_scope" to generate additional classes or + // other declarations that should be placed in this scope. + // + // Note that if the line containing the insertion point begins with + // whitespace, the same whitespace will be added to every line of the + // inserted text. This is useful for languages like Python, where + // indentation matters. In these languages, the insertion point comment + // should be indented the same amount as any inserted code will need to be + // in order to work correctly in that context. + // + // The code generator that generates the initial file and the one which + // inserts into it must both run as part of a single invocation of protoc. + // Code generators are executed in the order in which they appear on the + // command line. + // + // If |insertion_point| is present, |name| must also be present. + optional string insertion_point = 2; + + // The file contents. + optional string content = 15; + + // Information describing the file content being inserted. If an insertion + // point is used, this information will be appropriately offset and inserted + // into the code generation metadata for the generated files. + optional GeneratedCodeInfo generated_code_info = 16; + } + repeated File file = 15; +} diff --git a/api/protocol/include/google/protobuf/descriptor.proto b/api/protocol/include/google/protobuf/descriptor.proto new file mode 100644 index 0000000..9f0ce6c --- /dev/null +++ b/api/protocol/include/google/protobuf/descriptor.proto @@ -0,0 +1,909 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + optional string syntax = 12; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must be belong to a oneof to + // signal to old proto3 clients that presence is tracked for this field. This + // oneof is known as a "synthetic" oneof, and this field must be its sole + // member (each proto3 optional field gets its own synthetic oneof). Synthetic + // oneofs exist in the descriptor only, and do not generate any API. Synthetic + // oneofs must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + optional string java_outer_classname = 8; + + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + + + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + optional bool lazy = 5 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + // "foo.(bar.baz).qux". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified offset. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + } +} diff --git a/api/protocol/include/google/protobuf/duration.proto b/api/protocol/include/google/protobuf/duration.proto new file mode 100644 index 0000000..81c3e36 --- /dev/null +++ b/api/protocol/include/google/protobuf/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/api/protocol/include/google/protobuf/empty.proto b/api/protocol/include/google/protobuf/empty.proto new file mode 100644 index 0000000..5f992de --- /dev/null +++ b/api/protocol/include/google/protobuf/empty.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/emptypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +// The JSON representation for `Empty` is empty JSON object `{}`. +message Empty {} diff --git a/api/protocol/include/google/protobuf/field_mask.proto b/api/protocol/include/google/protobuf/field_mask.proto new file mode 100644 index 0000000..6b5104f --- /dev/null +++ b/api/protocol/include/google/protobuf/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/api/protocol/include/google/protobuf/source_context.proto b/api/protocol/include/google/protobuf/source_context.proto new file mode 100644 index 0000000..06bfc43 --- /dev/null +++ b/api/protocol/include/google/protobuf/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/api/protocol/include/google/protobuf/struct.proto b/api/protocol/include/google/protobuf/struct.proto new file mode 100644 index 0000000..545215c --- /dev/null +++ b/api/protocol/include/google/protobuf/struct.proto @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/structpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Struct` represents a structured data value, consisting of fields +// which map to dynamically typed values. In some languages, `Struct` +// might be supported by a native representation. For example, in +// scripting languages like JS a struct is represented as an +// object. The details of that representation are described together +// with the proto support for the language. +// +// The JSON representation for `Struct` is JSON object. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of that +// variants, absence of any variant indicates an error. +// +// The JSON representation for `Value` is JSON value. +message Value { + // The kind of value. + oneof kind { + // Represents a null value. + NullValue null_value = 1; + // Represents a double value. + double number_value = 2; + // Represents a string value. + string string_value = 3; + // Represents a boolean value. + bool bool_value = 4; + // Represents a structured value. + Struct struct_value = 5; + // Represents a repeated `Value`. + ListValue list_value = 6; + } +} + +// `NullValue` is a singleton enumeration to represent the null value for the +// `Value` type union. +// +// The JSON representation for `NullValue` is JSON `null`. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// `ListValue` is a wrapper around a repeated field of values. +// +// The JSON representation for `ListValue` is JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/api/protocol/include/google/protobuf/timestamp.proto b/api/protocol/include/google/protobuf/timestamp.proto new file mode 100644 index 0000000..3b2df6d --- /dev/null +++ b/api/protocol/include/google/protobuf/timestamp.proto @@ -0,0 +1,147 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D +// ) to obtain a formatter capable of generating timestamps in this format. +// +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/api/protocol/include/google/protobuf/type.proto b/api/protocol/include/google/protobuf/type.proto new file mode 100644 index 0000000..d3f6a68 --- /dev/null +++ b/api/protocol/include/google/protobuf/type.proto @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/protobuf/types/known/typepb"; + +// A protocol buffer message type. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; +} + +// A single field of a message type. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + } + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; +} + +// Enum value definition. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; +} diff --git a/api/protocol/include/google/protobuf/wrappers.proto b/api/protocol/include/google/protobuf/wrappers.proto new file mode 100644 index 0000000..d49dd53 --- /dev/null +++ b/api/protocol/include/google/protobuf/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/api/protocol/msg_proto/gen.go b/api/protocol/msg_proto/gen.go new file mode 100644 index 0000000..947998e --- /dev/null +++ b/api/protocol/msg_proto/gen.go @@ -0,0 +1,20 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package msg_proto + +////go:generate protoc -I=../ -I=. -I=../include --go_out=paths=source_relative:. msg.proto +//go:generate protoc -I=../ -I=. -I=../include --go-cast_out=paths=source_relative:. msg.proto diff --git a/api/protocol/msg_proto/msg.pb.go b/api/protocol/msg_proto/msg.pb.go new file mode 100644 index 0000000..9210e5e --- /dev/null +++ b/api/protocol/msg_proto/msg.pb.go @@ -0,0 +1,512 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.20.0 +// source: msg.proto + +package msg_proto + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MessageData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientVersion string `protobuf:"bytes,2,opt,name=clientVersion,proto3" json:"clientVersion,omitempty"` //client version + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` //unix time + Id string `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"` // message id hash256(payload) + NodeID string `protobuf:"bytes,5,opt,name=nodeID,proto3" json:"nodeID,omitempty"` //id of peer that created the message, not the peer that may have send it. + NodePubKey []byte `protobuf:"bytes,6,opt,name=nodePubKey,proto3" json:"nodePubKey,omitempty"` // peer public key + Sign []byte `protobuf:"bytes,7,opt,name=sign,proto3" json:"sign,omitempty"` // signature of message data + Payload []byte `protobuf:"bytes,8,opt,name=payload,proto3" json:"payload,omitempty"` // payload + Gossip bool `protobuf:"varint,9,opt,name=gossip,proto3" json:"gossip,omitempty"` +} + +func (x *MessageData) Reset() { + *x = MessageData{} + if protoimpl.UnsafeEnabled { + mi := &file_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageData) ProtoMessage() {} + +func (x *MessageData) ProtoReflect() protoreflect.Message { + mi := &file_msg_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageData.ProtoReflect.Descriptor instead. +func (*MessageData) Descriptor() ([]byte, []int) { + return file_msg_proto_rawDescGZIP(), []int{0} +} + +func (x *MessageData) GetClientVersion() string { + if x != nil { + return x.ClientVersion + } + return "" +} + +func (x *MessageData) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *MessageData) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *MessageData) GetNodeID() string { + if x != nil { + return x.NodeID + } + return "" +} + +func (x *MessageData) GetNodePubKey() []byte { + if x != nil { + return x.NodePubKey + } + return nil +} + +func (x *MessageData) GetSign() []byte { + if x != nil { + return x.Sign + } + return nil +} + +func (x *MessageData) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *MessageData) GetGossip() bool { + if x != nil { + return x.Gossip + } + return false +} + +type NewBlockMessageData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash *types_pb.H256 `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Number *types_pb.H256 `protobuf:"bytes,2,opt,name=Number,proto3" json:"Number,omitempty"` + Block *types_pb.Block `protobuf:"bytes,3,opt,name=block,proto3" json:"block,omitempty"` +} + +func (x *NewBlockMessageData) Reset() { + *x = NewBlockMessageData{} + if protoimpl.UnsafeEnabled { + mi := &file_msg_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NewBlockMessageData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NewBlockMessageData) ProtoMessage() {} + +func (x *NewBlockMessageData) ProtoReflect() protoreflect.Message { + mi := &file_msg_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NewBlockMessageData.ProtoReflect.Descriptor instead. +func (*NewBlockMessageData) Descriptor() ([]byte, []int) { + return file_msg_proto_rawDescGZIP(), []int{1} +} + +func (x *NewBlockMessageData) GetHash() *types_pb.H256 { + if x != nil { + return x.Hash + } + return nil +} + +func (x *NewBlockMessageData) GetNumber() *types_pb.H256 { + if x != nil { + return x.Number + } + return nil +} + +func (x *NewBlockMessageData) GetBlock() *types_pb.Block { + if x != nil { + return x.Block + } + return nil +} + +type ProtocolHandshakeMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + GenesisHash *types_pb.H256 `protobuf:"bytes,2,opt,name=genesisHash,proto3" json:"genesisHash,omitempty"` + CurrentHeight *types_pb.H256 `protobuf:"bytes,3,opt,name=currentHeight,proto3" json:"currentHeight,omitempty"` +} + +func (x *ProtocolHandshakeMessage) Reset() { + *x = ProtocolHandshakeMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_msg_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProtocolHandshakeMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProtocolHandshakeMessage) ProtoMessage() {} + +func (x *ProtocolHandshakeMessage) ProtoReflect() protoreflect.Message { + mi := &file_msg_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtocolHandshakeMessage.ProtoReflect.Descriptor instead. +func (*ProtocolHandshakeMessage) Descriptor() ([]byte, []int) { + return file_msg_proto_rawDescGZIP(), []int{2} +} + +func (x *ProtocolHandshakeMessage) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *ProtocolHandshakeMessage) GetGenesisHash() *types_pb.H256 { + if x != nil { + return x.GenesisHash + } + return nil +} + +func (x *ProtocolHandshakeMessage) GetCurrentHeight() *types_pb.H256 { + if x != nil { + return x.CurrentHeight + } + return nil +} + +type TopicScoreSnapshot struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Time a peer has spent in the gossip mesh. + TimeInMesh uint64 `protobuf:"varint,1,opt,name=time_in_mesh,json=timeInMesh,proto3" json:"time_in_mesh,omitempty"` + // This is the number of first message deliveries in the topic. + FirstMessageDeliveries float32 `protobuf:"fixed32,2,opt,name=first_message_deliveries,json=firstMessageDeliveries,proto3" json:"first_message_deliveries,omitempty"` + // This is the number of message deliveries in the mesh, within the MeshMessageDeliveriesWindow of + // message validation.It effectively tracks first and near-first + // deliveries, ie a message seen from a mesh peer before we have forwarded it to them. + MeshMessageDeliveries float32 `protobuf:"fixed32,3,opt,name=mesh_message_deliveries,json=meshMessageDeliveries,proto3" json:"mesh_message_deliveries,omitempty"` + // This is the number of invalid messages in the topic from the peer. + InvalidMessageDeliveries float32 `protobuf:"fixed32,4,opt,name=invalid_message_deliveries,json=invalidMessageDeliveries,proto3" json:"invalid_message_deliveries,omitempty"` +} + +func (x *TopicScoreSnapshot) Reset() { + *x = TopicScoreSnapshot{} + if protoimpl.UnsafeEnabled { + mi := &file_msg_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TopicScoreSnapshot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicScoreSnapshot) ProtoMessage() {} + +func (x *TopicScoreSnapshot) ProtoReflect() protoreflect.Message { + mi := &file_msg_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicScoreSnapshot.ProtoReflect.Descriptor instead. +func (*TopicScoreSnapshot) Descriptor() ([]byte, []int) { + return file_msg_proto_rawDescGZIP(), []int{3} +} + +func (x *TopicScoreSnapshot) GetTimeInMesh() uint64 { + if x != nil { + return x.TimeInMesh + } + return 0 +} + +func (x *TopicScoreSnapshot) GetFirstMessageDeliveries() float32 { + if x != nil { + return x.FirstMessageDeliveries + } + return 0 +} + +func (x *TopicScoreSnapshot) GetMeshMessageDeliveries() float32 { + if x != nil { + return x.MeshMessageDeliveries + } + return 0 +} + +func (x *TopicScoreSnapshot) GetInvalidMessageDeliveries() float32 { + if x != nil { + return x.InvalidMessageDeliveries + } + return 0 +} + +var File_msg_proto protoreflect.FileDescriptor + +var file_msg_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x73, 0x67, + 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x01, 0x0a, + 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, + 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, + 0x64, 0x65, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x6e, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x22, 0x88, + 0x01, 0x0a, 0x13, 0x4e, 0x65, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x48, 0x32, 0x35, 0x36, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x06, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x4e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x12, 0x25, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x9c, 0x01, 0x0a, 0x18, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x30, 0x0a, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x34, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xe6, 0x01, 0x0a, 0x12, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, + 0x20, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x4d, 0x65, 0x73, + 0x68, 0x12, 0x38, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x02, 0x52, 0x16, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x6d, + 0x65, 0x73, 0x68, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x69, + 0x76, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x15, 0x6d, 0x65, + 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x18, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, 0x6d, 0x63, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x6d, 0x73, 0x67, 0x5f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_msg_proto_rawDescOnce sync.Once + file_msg_proto_rawDescData = file_msg_proto_rawDesc +) + +func file_msg_proto_rawDescGZIP() []byte { + file_msg_proto_rawDescOnce.Do(func() { + file_msg_proto_rawDescData = protoimpl.X.CompressGZIP(file_msg_proto_rawDescData) + }) + return file_msg_proto_rawDescData +} + +var file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_msg_proto_goTypes = []interface{}{ + (*MessageData)(nil), // 0: msg_proto.MessageData + (*NewBlockMessageData)(nil), // 1: msg_proto.NewBlockMessageData + (*ProtocolHandshakeMessage)(nil), // 2: msg_proto.ProtocolHandshakeMessage + (*TopicScoreSnapshot)(nil), // 3: msg_proto.TopicScoreSnapshot + (*types_pb.H256)(nil), // 4: types_pb.H256 + (*types_pb.Block)(nil), // 5: types_pb.Block +} +var file_msg_proto_depIdxs = []int32{ + 4, // 0: msg_proto.NewBlockMessageData.hash:type_name -> types_pb.H256 + 4, // 1: msg_proto.NewBlockMessageData.Number:type_name -> types_pb.H256 + 5, // 2: msg_proto.NewBlockMessageData.block:type_name -> types_pb.Block + 4, // 3: msg_proto.ProtocolHandshakeMessage.genesisHash:type_name -> types_pb.H256 + 4, // 4: msg_proto.ProtocolHandshakeMessage.currentHeight:type_name -> types_pb.H256 + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_msg_proto_init() } +func file_msg_proto_init() { + if File_msg_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_msg_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_msg_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NewBlockMessageData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_msg_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProtocolHandshakeMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_msg_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TopicScoreSnapshot); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_msg_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_msg_proto_goTypes, + DependencyIndexes: file_msg_proto_depIdxs, + MessageInfos: file_msg_proto_msgTypes, + }.Build() + File_msg_proto = out.File + file_msg_proto_rawDesc = nil + file_msg_proto_goTypes = nil + file_msg_proto_depIdxs = nil +} diff --git a/api/protocol/msg_proto/msg.proto b/api/protocol/msg_proto/msg.proto new file mode 100644 index 0000000..d73337d --- /dev/null +++ b/api/protocol/msg_proto/msg.proto @@ -0,0 +1,58 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package msg_proto; + +import "types_pb/types.proto"; +option go_package = "github.com/astranetworld/ast/api/protocol/msg_proto"; + +message MessageData { + string clientVersion = 2; //client version + int64 timestamp = 3; //unix time + string id = 4; // message id hash256(payload) + string nodeID = 5; //id of peer that created the message, not the peer that may have send it. + bytes nodePubKey = 6; // peer public key + bytes sign = 7; // signature of message data + bytes payload = 8; // payload + bool gossip = 9; +} + +message NewBlockMessageData { + types_pb.H256 hash = 1 ; + types_pb.H256 Number = 2; + types_pb.Block block = 3; +} + +message ProtocolHandshakeMessage { + string version = 1; + types_pb.H256 genesisHash = 2; + types_pb.H256 currentHeight = 3; +} + + +message TopicScoreSnapshot { + // Time a peer has spent in the gossip mesh. + uint64 time_in_mesh = 1; + // This is the number of first message deliveries in the topic. + float first_message_deliveries = 2; + // This is the number of message deliveries in the mesh, within the MeshMessageDeliveriesWindow of + // message validation.It effectively tracks first and near-first + // deliveries, ie a message seen from a mesh peer before we have forwarded it to them. + float mesh_message_deliveries = 3; + // This is the number of invalid messages in the topic from the peer. + float invalid_message_deliveries = 4; +} diff --git a/api/protocol/state/gen.go b/api/protocol/state/gen.go new file mode 100644 index 0000000..503eb98 --- /dev/null +++ b/api/protocol/state/gen.go @@ -0,0 +1,19 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package state + +//go:generate protoc -I=../ -I=. -I=../include --go_out=paths=source_relative:. state.proto diff --git a/api/protocol/state/state.pb.go b/api/protocol/state/state.pb.go new file mode 100644 index 0000000..5d87108 --- /dev/null +++ b/api/protocol/state/state.pb.go @@ -0,0 +1,293 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.20.0 +// source: state.proto + +package state + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HashMap struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *types_pb.H256 `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value *types_pb.H256 `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *HashMap) Reset() { + *x = HashMap{} + if protoimpl.UnsafeEnabled { + mi := &file_state_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HashMap) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HashMap) ProtoMessage() {} + +func (x *HashMap) ProtoReflect() protoreflect.Message { + mi := &file_state_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HashMap.ProtoReflect.Descriptor instead. +func (*HashMap) Descriptor() ([]byte, []int) { + return file_state_proto_rawDescGZIP(), []int{0} +} + +func (x *HashMap) GetKey() *types_pb.H256 { + if x != nil { + return x.Key + } + return nil +} + +func (x *HashMap) GetValue() *types_pb.H256 { + if x != nil { + return x.Value + } + return nil +} + +type Account struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Initialised bool `protobuf:"varint,1,opt,name=Initialised,proto3" json:"Initialised,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=Nonce,proto3" json:"Nonce,omitempty"` + Balance *types_pb.H256 `protobuf:"bytes,3,opt,name=Balance,proto3" json:"Balance,omitempty"` + Root *types_pb.H256 `protobuf:"bytes,4,opt,name=Root,proto3" json:"Root,omitempty"` + CodeHash *types_pb.H256 `protobuf:"bytes,5,opt,name=CodeHash,proto3" json:"CodeHash,omitempty"` + Incarnation uint64 `protobuf:"varint,6,opt,name=Incarnation,proto3" json:"Incarnation,omitempty"` +} + +func (x *Account) Reset() { + *x = Account{} + if protoimpl.UnsafeEnabled { + mi := &file_state_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Account) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Account) ProtoMessage() {} + +func (x *Account) ProtoReflect() protoreflect.Message { + mi := &file_state_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Account.ProtoReflect.Descriptor instead. +func (*Account) Descriptor() ([]byte, []int) { + return file_state_proto_rawDescGZIP(), []int{1} +} + +func (x *Account) GetInitialised() bool { + if x != nil { + return x.Initialised + } + return false +} + +func (x *Account) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *Account) GetBalance() *types_pb.H256 { + if x != nil { + return x.Balance + } + return nil +} + +func (x *Account) GetRoot() *types_pb.H256 { + if x != nil { + return x.Root + } + return nil +} + +func (x *Account) GetCodeHash() *types_pb.H256 { + if x != nil { + return x.CodeHash + } + return nil +} + +func (x *Account) GetIncarnation() uint64 { + if x != nil { + return x.Incarnation + } + return 0 +} + +var File_state_proto protoreflect.FileDescriptor + +var file_state_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x62, 0x1a, 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x51, 0x0a, + 0x07, 0x48, 0x61, 0x73, 0x68, 0x4d, 0x61, 0x70, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0xdd, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, + 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x4e, + 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x07, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x22, + 0x0a, 0x04, 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x04, 0x52, 0x6f, + 0x6f, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x43, 0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x48, 0x32, 0x35, 0x36, 0x52, 0x08, 0x43, 0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, + 0x0a, 0x0b, 0x49, 0x6e, 0x63, 0x61, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x49, 0x6e, 0x63, 0x61, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, + 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, 0x6d, 0x63, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_state_proto_rawDescOnce sync.Once + file_state_proto_rawDescData = file_state_proto_rawDesc +) + +func file_state_proto_rawDescGZIP() []byte { + file_state_proto_rawDescOnce.Do(func() { + file_state_proto_rawDescData = protoimpl.X.CompressGZIP(file_state_proto_rawDescData) + }) + return file_state_proto_rawDescData +} + +var file_state_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_state_proto_goTypes = []interface{}{ + (*HashMap)(nil), // 0: state_pb.HashMap + (*Account)(nil), // 1: state_pb.Account + (*types_pb.H256)(nil), // 2: types_pb.H256 +} +var file_state_proto_depIdxs = []int32{ + 2, // 0: state_pb.HashMap.key:type_name -> types_pb.H256 + 2, // 1: state_pb.HashMap.value:type_name -> types_pb.H256 + 2, // 2: state_pb.Account.Balance:type_name -> types_pb.H256 + 2, // 3: state_pb.Account.Root:type_name -> types_pb.H256 + 2, // 4: state_pb.Account.CodeHash:type_name -> types_pb.H256 + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_state_proto_init() } +func file_state_proto_init() { + if File_state_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_state_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HashMap); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_state_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Account); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_state_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_state_proto_goTypes, + DependencyIndexes: file_state_proto_depIdxs, + MessageInfos: file_state_proto_msgTypes, + }.Build() + File_state_proto = out.File + file_state_proto_rawDesc = nil + file_state_proto_goTypes = nil + file_state_proto_depIdxs = nil +} diff --git a/api/protocol/state/state.proto b/api/protocol/state/state.proto new file mode 100644 index 0000000..ee13d03 --- /dev/null +++ b/api/protocol/state/state.proto @@ -0,0 +1,35 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package state_pb; + +import "types_pb/types.proto"; +option go_package = "github.com/astranetworld/ast/api/protocol/state"; + +message HashMap { + types_pb.H256 key = 1; + types_pb.H256 value = 2 ; +} + +message Account { + bool Initialised = 1; + uint64 Nonce = 2; + types_pb.H256 Balance = 3; + types_pb.H256 Root = 4; + types_pb.H256 CodeHash = 5; + uint64 Incarnation = 6; +} diff --git a/api/protocol/sync_pb/gen.go b/api/protocol/sync_pb/gen.go new file mode 100644 index 0000000..dfcd84e --- /dev/null +++ b/api/protocol/sync_pb/gen.go @@ -0,0 +1,21 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package sync_pb + +////go:generate protoc --plugin=/Users/mac/go/bin/protoc-gen-go-cast -I=../ -I=. -I=../include --go-cast_out=plugins=protoc-gen-go-cast,paths=source_relative:. types.proto +//go:generate protoc -I=../ -I=. -I=../include --go-cast_out=paths=source_relative:. sync_pb.proto +//go:generate sszgen -path=. -objs=BodiesByRangeRequest,HeadersByRangeRequest,Ping,ForkData,Status --include=../types_pb -output=generated.ssz.go diff --git a/api/protocol/sync_pb/generated.ssz.go b/api/protocol/sync_pb/generated.ssz.go new file mode 100644 index 0000000..ac21d2c --- /dev/null +++ b/api/protocol/sync_pb/generated.ssz.go @@ -0,0 +1,549 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: b39bb565b34add934304fdd716d14cb913acca74f226a8fd1f0c0c390e3bc247 +package sync_pb + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + ssz "github.com/prysmaticlabs/fastssz" +) + +// MarshalSSZ ssz marshals the HeadersByRangeRequest object +func (h *HeadersByRangeRequest) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the HeadersByRangeRequest object to a target array +func (h *HeadersByRangeRequest) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(20) + + // Offset (0) 'StartBlockNumber' + dst = ssz.WriteOffset(dst, offset) + if h.StartBlockNumber == nil { + h.StartBlockNumber = new(types_pb.H256) + } + offset += h.StartBlockNumber.SizeSSZ() + + // Field (1) 'Count' + dst = ssz.MarshalUint64(dst, h.Count) + + // Field (2) 'Step' + dst = ssz.MarshalUint64(dst, h.Step) + + // Field (0) 'StartBlockNumber' + if dst, err = h.StartBlockNumber.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the HeadersByRangeRequest object +func (h *HeadersByRangeRequest) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 20 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'StartBlockNumber' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 20 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Count' + h.Count = ssz.UnmarshallUint64(buf[4:12]) + + // Field (2) 'Step' + h.Step = ssz.UnmarshallUint64(buf[12:20]) + + // Field (0) 'StartBlockNumber' + { + buf = tail[o0:] + if h.StartBlockNumber == nil { + h.StartBlockNumber = new(types_pb.H256) + } + if err = h.StartBlockNumber.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the HeadersByRangeRequest object +func (h *HeadersByRangeRequest) SizeSSZ() (size int) { + size = 20 + + // Field (0) 'StartBlockNumber' + if h.StartBlockNumber == nil { + h.StartBlockNumber = new(types_pb.H256) + } + size += h.StartBlockNumber.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the HeadersByRangeRequest object +func (h *HeadersByRangeRequest) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the HeadersByRangeRequest object with a hasher +func (h *HeadersByRangeRequest) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'StartBlockNumber' + if err = h.StartBlockNumber.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Count' + hh.PutUint64(h.Count) + + // Field (2) 'Step' + hh.PutUint64(h.Step) + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Ping object +func (p *Ping) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the Ping object to a target array +func (p *Ping) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'SeqNumber' + dst = ssz.MarshalUint64(dst, p.SeqNumber) + + return +} + +// UnmarshalSSZ ssz unmarshals the Ping object +func (p *Ping) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 8 { + return ssz.ErrSize + } + + // Field (0) 'SeqNumber' + p.SeqNumber = ssz.UnmarshallUint64(buf[0:8]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Ping object +func (p *Ping) SizeSSZ() (size int) { + size = 8 + return +} + +// HashTreeRoot ssz hashes the Ping object +func (p *Ping) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the Ping object with a hasher +func (p *Ping) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'SeqNumber' + hh.PutUint64(p.SeqNumber) + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Status object +func (s *Status) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the Status object to a target array +func (s *Status) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(8) + + // Offset (0) 'GenesisHash' + dst = ssz.WriteOffset(dst, offset) + if s.GenesisHash == nil { + s.GenesisHash = new(types_pb.H256) + } + offset += s.GenesisHash.SizeSSZ() + + // Offset (1) 'CurrentHeight' + dst = ssz.WriteOffset(dst, offset) + if s.CurrentHeight == nil { + s.CurrentHeight = new(types_pb.H256) + } + offset += s.CurrentHeight.SizeSSZ() + + // Field (0) 'GenesisHash' + if dst, err = s.GenesisHash.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'CurrentHeight' + if dst, err = s.CurrentHeight.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Status object +func (s *Status) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 8 { + return ssz.ErrSize + } + + tail := buf + var o0, o1 uint64 + + // Offset (0) 'GenesisHash' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 8 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'CurrentHeight' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Field (0) 'GenesisHash' + { + buf = tail[o0:o1] + if s.GenesisHash == nil { + s.GenesisHash = new(types_pb.H256) + } + if err = s.GenesisHash.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (1) 'CurrentHeight' + { + buf = tail[o1:] + if s.CurrentHeight == nil { + s.CurrentHeight = new(types_pb.H256) + } + if err = s.CurrentHeight.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Status object +func (s *Status) SizeSSZ() (size int) { + size = 8 + + // Field (0) 'GenesisHash' + if s.GenesisHash == nil { + s.GenesisHash = new(types_pb.H256) + } + size += s.GenesisHash.SizeSSZ() + + // Field (1) 'CurrentHeight' + if s.CurrentHeight == nil { + s.CurrentHeight = new(types_pb.H256) + } + size += s.CurrentHeight.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the Status object +func (s *Status) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the Status object with a hasher +func (s *Status) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'GenesisHash' + if err = s.GenesisHash.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'CurrentHeight' + if err = s.CurrentHeight.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the ForkData object +func (f *ForkData) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(f) +} + +// MarshalSSZTo ssz marshals the ForkData object to a target array +func (f *ForkData) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(8) + + // Offset (0) 'CurrentVersion' + dst = ssz.WriteOffset(dst, offset) + if f.CurrentVersion == nil { + f.CurrentVersion = new(types_pb.H256) + } + offset += f.CurrentVersion.SizeSSZ() + + // Offset (1) 'GenesisValidatorsRoot' + dst = ssz.WriteOffset(dst, offset) + if f.GenesisValidatorsRoot == nil { + f.GenesisValidatorsRoot = new(types_pb.H256) + } + offset += f.GenesisValidatorsRoot.SizeSSZ() + + // Field (0) 'CurrentVersion' + if dst, err = f.CurrentVersion.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'GenesisValidatorsRoot' + if dst, err = f.GenesisValidatorsRoot.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the ForkData object +func (f *ForkData) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 8 { + return ssz.ErrSize + } + + tail := buf + var o0, o1 uint64 + + // Offset (0) 'CurrentVersion' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 8 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'GenesisValidatorsRoot' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Field (0) 'CurrentVersion' + { + buf = tail[o0:o1] + if f.CurrentVersion == nil { + f.CurrentVersion = new(types_pb.H256) + } + if err = f.CurrentVersion.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (1) 'GenesisValidatorsRoot' + { + buf = tail[o1:] + if f.GenesisValidatorsRoot == nil { + f.GenesisValidatorsRoot = new(types_pb.H256) + } + if err = f.GenesisValidatorsRoot.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ForkData object +func (f *ForkData) SizeSSZ() (size int) { + size = 8 + + // Field (0) 'CurrentVersion' + if f.CurrentVersion == nil { + f.CurrentVersion = new(types_pb.H256) + } + size += f.CurrentVersion.SizeSSZ() + + // Field (1) 'GenesisValidatorsRoot' + if f.GenesisValidatorsRoot == nil { + f.GenesisValidatorsRoot = new(types_pb.H256) + } + size += f.GenesisValidatorsRoot.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the ForkData object +func (f *ForkData) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(f) +} + +// HashTreeRootWith ssz hashes the ForkData object with a hasher +func (f *ForkData) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'CurrentVersion' + if err = f.CurrentVersion.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'GenesisValidatorsRoot' + if err = f.GenesisValidatorsRoot.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the BodiesByRangeRequest object +func (b *BodiesByRangeRequest) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BodiesByRangeRequest object to a target array +func (b *BodiesByRangeRequest) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(20) + + // Offset (0) 'StartBlockNumber' + dst = ssz.WriteOffset(dst, offset) + if b.StartBlockNumber == nil { + b.StartBlockNumber = new(types_pb.H256) + } + offset += b.StartBlockNumber.SizeSSZ() + + // Field (1) 'Count' + dst = ssz.MarshalUint64(dst, b.Count) + + // Field (2) 'Step' + dst = ssz.MarshalUint64(dst, b.Step) + + // Field (0) 'StartBlockNumber' + if dst, err = b.StartBlockNumber.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BodiesByRangeRequest object +func (b *BodiesByRangeRequest) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 20 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'StartBlockNumber' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 20 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Count' + b.Count = ssz.UnmarshallUint64(buf[4:12]) + + // Field (2) 'Step' + b.Step = ssz.UnmarshallUint64(buf[12:20]) + + // Field (0) 'StartBlockNumber' + { + buf = tail[o0:] + if b.StartBlockNumber == nil { + b.StartBlockNumber = new(types_pb.H256) + } + if err = b.StartBlockNumber.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BodiesByRangeRequest object +func (b *BodiesByRangeRequest) SizeSSZ() (size int) { + size = 20 + + // Field (0) 'StartBlockNumber' + if b.StartBlockNumber == nil { + b.StartBlockNumber = new(types_pb.H256) + } + size += b.StartBlockNumber.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the BodiesByRangeRequest object +func (b *BodiesByRangeRequest) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BodiesByRangeRequest object with a hasher +func (b *BodiesByRangeRequest) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'StartBlockNumber' + if err = b.StartBlockNumber.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Count' + hh.PutUint64(b.Count) + + // Field (2) 'Step' + hh.PutUint64(b.Step) + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} diff --git a/api/protocol/sync_pb/sync_pb.pb.go b/api/protocol/sync_pb/sync_pb.pb.go new file mode 100644 index 0000000..3d47a3d --- /dev/null +++ b/api/protocol/sync_pb/sync_pb.pb.go @@ -0,0 +1,492 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.20.0 +// source: sync_pb.proto + +package sync_pb + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HeadersByRangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StartBlockNumber *types_pb.H256 `protobuf:"bytes,1,opt,name=startBlockNumber,proto3" json:"startBlockNumber,omitempty"` + Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + Step uint64 `protobuf:"varint,3,opt,name=step,proto3" json:"step,omitempty"` +} + +func (x *HeadersByRangeRequest) Reset() { + *x = HeadersByRangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_pb_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeadersByRangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeadersByRangeRequest) ProtoMessage() {} + +func (x *HeadersByRangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_sync_pb_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeadersByRangeRequest.ProtoReflect.Descriptor instead. +func (*HeadersByRangeRequest) Descriptor() ([]byte, []int) { + return file_sync_pb_proto_rawDescGZIP(), []int{0} +} + +func (x *HeadersByRangeRequest) GetStartBlockNumber() *types_pb.H256 { + if x != nil { + return x.StartBlockNumber + } + return nil +} + +func (x *HeadersByRangeRequest) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *HeadersByRangeRequest) GetStep() uint64 { + if x != nil { + return x.Step + } + return 0 +} + +// todo +type Ping struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SeqNumber uint64 `protobuf:"varint,1,opt,name=seq_number,json=seqNumber,proto3" json:"seq_number,omitempty"` +} + +func (x *Ping) Reset() { + *x = Ping{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_pb_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ping) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ping) ProtoMessage() {} + +func (x *Ping) ProtoReflect() protoreflect.Message { + mi := &file_sync_pb_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ping.ProtoReflect.Descriptor instead. +func (*Ping) Descriptor() ([]byte, []int) { + return file_sync_pb_proto_rawDescGZIP(), []int{1} +} + +func (x *Ping) GetSeqNumber() uint64 { + if x != nil { + return x.SeqNumber + } + return 0 +} + +// v2 +type Status struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // string version = 1; + GenesisHash *types_pb.H256 `protobuf:"bytes,1,opt,name=genesisHash,proto3" json:"genesisHash,omitempty"` + CurrentHeight *types_pb.H256 `protobuf:"bytes,2,opt,name=currentHeight,proto3" json:"currentHeight,omitempty"` +} + +func (x *Status) Reset() { + *x = Status{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_pb_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_sync_pb_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_sync_pb_proto_rawDescGZIP(), []int{2} +} + +func (x *Status) GetGenesisHash() *types_pb.H256 { + if x != nil { + return x.GenesisHash + } + return nil +} + +func (x *Status) GetCurrentHeight() *types_pb.H256 { + if x != nil { + return x.CurrentHeight + } + return nil +} + +type ForkData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CurrentVersion *types_pb.H256 `protobuf:"bytes,1,opt,name=current_version,json=currentVersion,proto3" json:"current_version,omitempty"` + GenesisValidatorsRoot *types_pb.H256 `protobuf:"bytes,2,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3" json:"genesis_validators_root,omitempty"` +} + +func (x *ForkData) Reset() { + *x = ForkData{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_pb_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ForkData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForkData) ProtoMessage() {} + +func (x *ForkData) ProtoReflect() protoreflect.Message { + mi := &file_sync_pb_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForkData.ProtoReflect.Descriptor instead. +func (*ForkData) Descriptor() ([]byte, []int) { + return file_sync_pb_proto_rawDescGZIP(), []int{3} +} + +func (x *ForkData) GetCurrentVersion() *types_pb.H256 { + if x != nil { + return x.CurrentVersion + } + return nil +} + +func (x *ForkData) GetGenesisValidatorsRoot() *types_pb.H256 { + if x != nil { + return x.GenesisValidatorsRoot + } + return nil +} + +type BodiesByRangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StartBlockNumber *types_pb.H256 `protobuf:"bytes,1,opt,name=startBlockNumber,proto3" json:"startBlockNumber,omitempty"` + Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + Step uint64 `protobuf:"varint,3,opt,name=step,proto3" json:"step,omitempty"` +} + +func (x *BodiesByRangeRequest) Reset() { + *x = BodiesByRangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_pb_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BodiesByRangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BodiesByRangeRequest) ProtoMessage() {} + +func (x *BodiesByRangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_sync_pb_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BodiesByRangeRequest.ProtoReflect.Descriptor instead. +func (*BodiesByRangeRequest) Descriptor() ([]byte, []int) { + return file_sync_pb_proto_rawDescGZIP(), []int{4} +} + +func (x *BodiesByRangeRequest) GetStartBlockNumber() *types_pb.H256 { + if x != nil { + return x.StartBlockNumber + } + return nil +} + +func (x *BodiesByRangeRequest) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *BodiesByRangeRequest) GetStep() uint64 { + if x != nil { + return x.Step + } + return 0 +} + +var File_sync_pb_proto protoreflect.FileDescriptor + +var file_sync_pb_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x07, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x62, 0x70, 0x1a, 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7d, + 0x0a, 0x15, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, + 0x36, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, + 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x22, 0x25, 0x0a, + 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x65, 0x71, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x22, 0x70, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, + 0x0a, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, + 0x32, 0x35, 0x36, 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x34, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x08, 0x46, 0x6f, 0x72, 0x6b, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x37, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x17, + 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x15, 0x67, + 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, + 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x7c, 0x0a, 0x14, 0x42, 0x6f, 0x64, 0x69, 0x65, 0x73, 0x42, 0x79, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x10, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x74, + 0x65, 0x70, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, 0x6d, 0x63, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x79, 0x6e, + 0x63, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_sync_pb_proto_rawDescOnce sync.Once + file_sync_pb_proto_rawDescData = file_sync_pb_proto_rawDesc +) + +func file_sync_pb_proto_rawDescGZIP() []byte { + file_sync_pb_proto_rawDescOnce.Do(func() { + file_sync_pb_proto_rawDescData = protoimpl.X.CompressGZIP(file_sync_pb_proto_rawDescData) + }) + return file_sync_pb_proto_rawDescData +} + +var file_sync_pb_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_sync_pb_proto_goTypes = []interface{}{ + (*HeadersByRangeRequest)(nil), // 0: sync_bp.HeadersByRangeRequest + (*Ping)(nil), // 1: sync_bp.Ping + (*Status)(nil), // 2: sync_bp.Status + (*ForkData)(nil), // 3: sync_bp.ForkData + (*BodiesByRangeRequest)(nil), // 4: sync_bp.BodiesByRangeRequest + (*types_pb.H256)(nil), // 5: types_pb.H256 +} +var file_sync_pb_proto_depIdxs = []int32{ + 5, // 0: sync_bp.HeadersByRangeRequest.startBlockNumber:type_name -> types_pb.H256 + 5, // 1: sync_bp.Status.genesisHash:type_name -> types_pb.H256 + 5, // 2: sync_bp.Status.currentHeight:type_name -> types_pb.H256 + 5, // 3: sync_bp.ForkData.current_version:type_name -> types_pb.H256 + 5, // 4: sync_bp.ForkData.genesis_validators_root:type_name -> types_pb.H256 + 5, // 5: sync_bp.BodiesByRangeRequest.startBlockNumber:type_name -> types_pb.H256 + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_sync_pb_proto_init() } +func file_sync_pb_proto_init() { + if File_sync_pb_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_sync_pb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeadersByRangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_pb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Ping); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_pb_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Status); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_pb_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForkData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_pb_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BodiesByRangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_sync_pb_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_sync_pb_proto_goTypes, + DependencyIndexes: file_sync_pb_proto_depIdxs, + MessageInfos: file_sync_pb_proto_msgTypes, + }.Build() + File_sync_pb_proto = out.File + file_sync_pb_proto_rawDesc = nil + file_sync_pb_proto_goTypes = nil + file_sync_pb_proto_depIdxs = nil +} diff --git a/api/protocol/sync_pb/sync_pb.proto b/api/protocol/sync_pb/sync_pb.proto new file mode 100644 index 0000000..1d7263d --- /dev/null +++ b/api/protocol/sync_pb/sync_pb.proto @@ -0,0 +1,50 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package sync_bp; +option go_package = "github.com/astranetworld/ast/api/protocol/sync_pb"; + +import "types_pb/types.proto"; + +message HeadersByRangeRequest { + types_pb.H256 startBlockNumber = 1; + uint64 count = 2; + uint64 step = 3; +} + +// todo +message Ping { + uint64 seq_number = 1; +} + +//v2 +message Status { + // string version = 1; + types_pb.H256 genesisHash = 1; + types_pb.H256 currentHeight = 2; +} + +message ForkData { + types_pb.H256 current_version = 1; + types_pb.H256 genesis_validators_root = 2; +} + +message BodiesByRangeRequest { + types_pb.H256 startBlockNumber = 1; + uint64 count = 2; + uint64 step = 3; +} diff --git a/api/protocol/sync_proto/gen.go b/api/protocol/sync_proto/gen.go new file mode 100644 index 0000000..c637b3f --- /dev/null +++ b/api/protocol/sync_proto/gen.go @@ -0,0 +1,19 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package sync_proto + +//go:generate protoc -I=../ -I=. -I=../include --go_out=paths=source_relative:. sync.proto diff --git a/api/protocol/sync_proto/sync.pb.go b/api/protocol/sync_proto/sync.pb.go new file mode 100644 index 0000000..e9887eb --- /dev/null +++ b/api/protocol/sync_proto/sync.pb.go @@ -0,0 +1,1045 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.20.0 +// source: sync_pb.proto + +package sync_proto + +import ( + types_pb "github.com/astranetworld/ast/api/protocol/types_pb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SyncType int32 + +const ( + SyncType_FINDReq SyncType = 0 + SyncType_FindRes SyncType = 1 + SyncType_HeaderReq SyncType = 2 + SyncType_HeaderRes SyncType = 3 + SyncType_BodyReq SyncType = 4 + SyncType_BodyRes SyncType = 5 + SyncType_StateReq SyncType = 6 + SyncType_StateRes SyncType = 7 + SyncType_TransactionReq SyncType = 8 + SyncType_TransactionRes SyncType = 9 + SyncType_PeerInfoBroadcast SyncType = 10 +) + +// Enum value maps for SyncType. +var ( + SyncType_name = map[int32]string{ + 0: "FINDReq", + 1: "FindRes", + 2: "HeaderReq", + 3: "HeaderRes", + 4: "BodyReq", + 5: "BodyRes", + 6: "StateReq", + 7: "StateRes", + 8: "TransactionReq", + 9: "TransactionRes", + 10: "PeerInfoBroadcast", + } + SyncType_value = map[string]int32{ + "FINDReq": 0, + "FindRes": 1, + "HeaderReq": 2, + "HeaderRes": 3, + "BodyReq": 4, + "BodyRes": 5, + "StateReq": 6, + "StateRes": 7, + "TransactionReq": 8, + "TransactionRes": 9, + "PeerInfoBroadcast": 10, + } +) + +func (x SyncType) Enum() *SyncType { + p := new(SyncType) + *p = x + return p +} + +func (x SyncType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SyncType) Descriptor() protoreflect.EnumDescriptor { + return file_sync_proto_enumTypes[0].Descriptor() +} + +func (SyncType) Type() protoreflect.EnumType { + return &file_sync_proto_enumTypes[0] +} + +func (x SyncType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SyncType.Descriptor instead. +func (SyncType) EnumDescriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{0} +} + +type SyncProtocol struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SyncProtocol) Reset() { + *x = SyncProtocol{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncProtocol) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncProtocol) ProtoMessage() {} + +func (x *SyncProtocol) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncProtocol.ProtoReflect.Descriptor instead. +func (*SyncProtocol) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{0} +} + +type Value struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` +} + +func (x *Value) Reset() { + *x = Value{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Value) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Value) ProtoMessage() {} + +func (x *Value) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Value.ProtoReflect.Descriptor instead. +func (*Value) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{1} +} + +func (x *Value) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *Value) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +type SyncBlockRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Number []*types_pb.H256 `protobuf:"bytes,1,rep,name=number,proto3" json:"number,omitempty"` +} + +func (x *SyncBlockRequest) Reset() { + *x = SyncBlockRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncBlockRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncBlockRequest) ProtoMessage() {} + +func (x *SyncBlockRequest) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncBlockRequest.ProtoReflect.Descriptor instead. +func (*SyncBlockRequest) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{2} +} + +func (x *SyncBlockRequest) GetNumber() []*types_pb.H256 { + if x != nil { + return x.Number + } + return nil +} + +type SyncBlockResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Blocks []*types_pb.Block `protobuf:"bytes,1,rep,name=blocks,proto3" json:"blocks,omitempty"` +} + +func (x *SyncBlockResponse) Reset() { + *x = SyncBlockResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncBlockResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncBlockResponse) ProtoMessage() {} + +func (x *SyncBlockResponse) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncBlockResponse.ProtoReflect.Descriptor instead. +func (*SyncBlockResponse) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{3} +} + +func (x *SyncBlockResponse) GetBlocks() []*types_pb.Block { + if x != nil { + return x.Blocks + } + return nil +} + +type SyncHeaderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Number *types_pb.H256 `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"` + Amount *types_pb.H256 `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *SyncHeaderRequest) Reset() { + *x = SyncHeaderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncHeaderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncHeaderRequest) ProtoMessage() {} + +func (x *SyncHeaderRequest) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncHeaderRequest.ProtoReflect.Descriptor instead. +func (*SyncHeaderRequest) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{4} +} + +func (x *SyncHeaderRequest) GetNumber() *types_pb.H256 { + if x != nil { + return x.Number + } + return nil +} + +func (x *SyncHeaderRequest) GetAmount() *types_pb.H256 { + if x != nil { + return x.Amount + } + return nil +} + +type SyncHeaderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Headers []*types_pb.Header `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` +} + +func (x *SyncHeaderResponse) Reset() { + *x = SyncHeaderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncHeaderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncHeaderResponse) ProtoMessage() {} + +func (x *SyncHeaderResponse) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncHeaderResponse.ProtoReflect.Descriptor instead. +func (*SyncHeaderResponse) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{5} +} + +func (x *SyncHeaderResponse) GetHeaders() []*types_pb.Header { + if x != nil { + return x.Headers + } + return nil +} + +type SyncTransactionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Bloom []byte `protobuf:"bytes,1,opt,name=bloom,proto3" json:"bloom,omitempty"` +} + +func (x *SyncTransactionRequest) Reset() { + *x = SyncTransactionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncTransactionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncTransactionRequest) ProtoMessage() {} + +func (x *SyncTransactionRequest) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncTransactionRequest.ProtoReflect.Descriptor instead. +func (*SyncTransactionRequest) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{6} +} + +func (x *SyncTransactionRequest) GetBloom() []byte { + if x != nil { + return x.Bloom + } + return nil +} + +type SyncTransactionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Transactions []*types_pb.Transaction `protobuf:"bytes,1,rep,name=transactions,proto3" json:"transactions,omitempty"` +} + +func (x *SyncTransactionResponse) Reset() { + *x = SyncTransactionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncTransactionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncTransactionResponse) ProtoMessage() {} + +func (x *SyncTransactionResponse) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncTransactionResponse.ProtoReflect.Descriptor instead. +func (*SyncTransactionResponse) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{7} +} + +func (x *SyncTransactionResponse) GetTransactions() []*types_pb.Transaction { + if x != nil { + return x.Transactions + } + return nil +} + +type SyncPeerInfoBroadcast struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Difficulty *types_pb.H256 `protobuf:"bytes,1,opt,name=Difficulty,proto3" json:"Difficulty,omitempty"` + Number *types_pb.H256 `protobuf:"bytes,2,opt,name=Number,proto3" json:"Number,omitempty"` +} + +func (x *SyncPeerInfoBroadcast) Reset() { + *x = SyncPeerInfoBroadcast{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncPeerInfoBroadcast) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncPeerInfoBroadcast) ProtoMessage() {} + +func (x *SyncPeerInfoBroadcast) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncPeerInfoBroadcast.ProtoReflect.Descriptor instead. +func (*SyncPeerInfoBroadcast) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{8} +} + +func (x *SyncPeerInfoBroadcast) GetDifficulty() *types_pb.H256 { + if x != nil { + return x.Difficulty + } + return nil +} + +func (x *SyncPeerInfoBroadcast) GetNumber() *types_pb.H256 { + if x != nil { + return x.Number + } + return nil +} + +type SyncTask struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // task id + Ok bool `protobuf:"varint,2,opt,name=ok,proto3" json:"ok,omitempty"` + SyncType SyncType `protobuf:"varint,3,opt,name=syncType,proto3,enum=sync_proto.SyncType" json:"syncType,omitempty"` + // Types that are assignable to Payload: + // *SyncTask_SyncHeaderRequest + // *SyncTask_SyncHeaderResponse + // *SyncTask_SyncBlockRequest + // *SyncTask_SyncBlockResponse + // *SyncTask_SyncTransactionRequest + // *SyncTask_SyncTransactionResponse + // *SyncTask_SyncPeerInfoBroadcast + Payload isSyncTask_Payload `protobuf_oneof:"payload"` +} + +func (x *SyncTask) Reset() { + *x = SyncTask{} + if protoimpl.UnsafeEnabled { + mi := &file_sync_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncTask) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncTask) ProtoMessage() {} + +func (x *SyncTask) ProtoReflect() protoreflect.Message { + mi := &file_sync_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncTask.ProtoReflect.Descriptor instead. +func (*SyncTask) Descriptor() ([]byte, []int) { + return file_sync_proto_rawDescGZIP(), []int{9} +} + +func (x *SyncTask) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SyncTask) GetOk() bool { + if x != nil { + return x.Ok + } + return false +} + +func (x *SyncTask) GetSyncType() SyncType { + if x != nil { + return x.SyncType + } + return SyncType_FINDReq +} + +func (m *SyncTask) GetPayload() isSyncTask_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *SyncTask) GetSyncHeaderRequest() *SyncHeaderRequest { + if x, ok := x.GetPayload().(*SyncTask_SyncHeaderRequest); ok { + return x.SyncHeaderRequest + } + return nil +} + +func (x *SyncTask) GetSyncHeaderResponse() *SyncHeaderResponse { + if x, ok := x.GetPayload().(*SyncTask_SyncHeaderResponse); ok { + return x.SyncHeaderResponse + } + return nil +} + +func (x *SyncTask) GetSyncBlockRequest() *SyncBlockRequest { + if x, ok := x.GetPayload().(*SyncTask_SyncBlockRequest); ok { + return x.SyncBlockRequest + } + return nil +} + +func (x *SyncTask) GetSyncBlockResponse() *SyncBlockResponse { + if x, ok := x.GetPayload().(*SyncTask_SyncBlockResponse); ok { + return x.SyncBlockResponse + } + return nil +} + +func (x *SyncTask) GetSyncTransactionRequest() *SyncTransactionRequest { + if x, ok := x.GetPayload().(*SyncTask_SyncTransactionRequest); ok { + return x.SyncTransactionRequest + } + return nil +} + +func (x *SyncTask) GetSyncTransactionResponse() *SyncTransactionResponse { + if x, ok := x.GetPayload().(*SyncTask_SyncTransactionResponse); ok { + return x.SyncTransactionResponse + } + return nil +} + +func (x *SyncTask) GetSyncPeerInfoBroadcast() *SyncPeerInfoBroadcast { + if x, ok := x.GetPayload().(*SyncTask_SyncPeerInfoBroadcast); ok { + return x.SyncPeerInfoBroadcast + } + return nil +} + +type isSyncTask_Payload interface { + isSyncTask_Payload() +} + +type SyncTask_SyncHeaderRequest struct { + //header + SyncHeaderRequest *SyncHeaderRequest `protobuf:"bytes,4,opt,name=syncHeaderRequest,proto3,oneof"` +} + +type SyncTask_SyncHeaderResponse struct { + SyncHeaderResponse *SyncHeaderResponse `protobuf:"bytes,5,opt,name=syncHeaderResponse,proto3,oneof"` +} + +type SyncTask_SyncBlockRequest struct { + //body + SyncBlockRequest *SyncBlockRequest `protobuf:"bytes,6,opt,name=syncBlockRequest,proto3,oneof"` +} + +type SyncTask_SyncBlockResponse struct { + SyncBlockResponse *SyncBlockResponse `protobuf:"bytes,7,opt,name=syncBlockResponse,proto3,oneof"` +} + +type SyncTask_SyncTransactionRequest struct { + //Transaction + SyncTransactionRequest *SyncTransactionRequest `protobuf:"bytes,8,opt,name=syncTransactionRequest,proto3,oneof"` +} + +type SyncTask_SyncTransactionResponse struct { + SyncTransactionResponse *SyncTransactionResponse `protobuf:"bytes,9,opt,name=syncTransactionResponse,proto3,oneof"` +} + +type SyncTask_SyncPeerInfoBroadcast struct { + // + SyncPeerInfoBroadcast *SyncPeerInfoBroadcast `protobuf:"bytes,10,opt,name=syncPeerInfoBroadcast,proto3,oneof"` +} + +func (*SyncTask_SyncHeaderRequest) isSyncTask_Payload() {} + +func (*SyncTask_SyncHeaderResponse) isSyncTask_Payload() {} + +func (*SyncTask_SyncBlockRequest) isSyncTask_Payload() {} + +func (*SyncTask_SyncBlockResponse) isSyncTask_Payload() {} + +func (*SyncTask_SyncTransactionRequest) isSyncTask_Payload() {} + +func (*SyncTask_SyncTransactionResponse) isSyncTask_Payload() {} + +func (*SyncTask_SyncPeerInfoBroadcast) isSyncTask_Payload() {} + +var File_sync_proto protoreflect.FileDescriptor + +var file_sync_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x73, 0x79, 0x6e, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x73, 0x79, + 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0e, + 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x33, + 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x22, 0x3a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, + 0x3c, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x63, 0x0a, + 0x11, 0x53, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, + 0x35, 0x36, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x40, 0x0a, 0x12, 0x53, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x22, 0x2e, 0x0a, 0x16, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, + 0x6c, 0x6f, 0x6f, 0x6d, 0x22, 0x54, 0x0a, 0x17, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x6f, 0x0a, 0x15, 0x53, 0x79, + 0x6e, 0x63, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0a, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, + 0x6c, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x06, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, + 0x32, 0x35, 0x36, 0x52, 0x06, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0xbd, 0x05, 0x0a, 0x08, + 0x53, 0x79, 0x6e, 0x63, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x30, 0x0a, 0x08, 0x73, 0x79, 0x6e, 0x63, + 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x73, 0x79, 0x6e, + 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x79, + 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x12, 0x73, 0x79, 0x6e, + 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x12, 0x73, 0x79, 0x6e, 0x63, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x10, 0x73, + 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x16, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x16, 0x73, 0x79, + 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x17, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x17, 0x73, 0x79, + 0x6e, 0x63, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x65, 0x65, + 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x72, + 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x00, 0x52, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x50, + 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, + 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2a, 0xb7, 0x01, 0x0a, 0x08, + 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x49, 0x4e, 0x44, + 0x52, 0x65, 0x71, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x69, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x10, + 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x10, 0x03, + 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x65, 0x71, 0x10, 0x04, 0x12, 0x0b, 0x0a, + 0x07, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x65, 0x73, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x10, 0x08, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x10, 0x09, 0x12, 0x15, + 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x10, 0x0a, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, + 0x6d, 0x63, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_sync_proto_rawDescOnce sync.Once + file_sync_proto_rawDescData = file_sync_proto_rawDesc +) + +func file_sync_proto_rawDescGZIP() []byte { + file_sync_proto_rawDescOnce.Do(func() { + file_sync_proto_rawDescData = protoimpl.X.CompressGZIP(file_sync_proto_rawDescData) + }) + return file_sync_proto_rawDescData +} + +var file_sync_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_sync_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_sync_proto_goTypes = []interface{}{ + (SyncType)(0), // 0: sync_proto.SyncType + (*SyncProtocol)(nil), // 1: sync_proto.SyncProtocol + (*Value)(nil), // 2: sync_proto.Value + (*SyncBlockRequest)(nil), // 3: sync_proto.SyncBlockRequest + (*SyncBlockResponse)(nil), // 4: sync_proto.SyncBlockResponse + (*SyncHeaderRequest)(nil), // 5: sync_proto.SyncHeaderRequest + (*SyncHeaderResponse)(nil), // 6: sync_proto.SyncHeaderResponse + (*SyncTransactionRequest)(nil), // 7: sync_proto.SyncTransactionRequest + (*SyncTransactionResponse)(nil), // 8: sync_proto.SyncTransactionResponse + (*SyncPeerInfoBroadcast)(nil), // 9: sync_proto.SyncPeerInfoBroadcast + (*SyncTask)(nil), // 10: sync_proto.SyncTask + (*types_pb.H256)(nil), // 11: types_pb.H256 + (*types_pb.Block)(nil), // 12: types_pb.Block + (*types_pb.Header)(nil), // 13: types_pb.Header + (*types_pb.Transaction)(nil), // 14: types_pb.Transaction +} +var file_sync_proto_depIdxs = []int32{ + 11, // 0: sync_proto.SyncBlockRequest.number:type_name -> types_pb.H256 + 12, // 1: sync_proto.SyncBlockResponse.blocks:type_name -> types_pb.Block + 11, // 2: sync_proto.SyncHeaderRequest.number:type_name -> types_pb.H256 + 11, // 3: sync_proto.SyncHeaderRequest.amount:type_name -> types_pb.H256 + 13, // 4: sync_proto.SyncHeaderResponse.headers:type_name -> types_pb.Header + 14, // 5: sync_proto.SyncTransactionResponse.transactions:type_name -> types_pb.Transaction + 11, // 6: sync_proto.SyncPeerInfoBroadcast.Difficulty:type_name -> types_pb.H256 + 11, // 7: sync_proto.SyncPeerInfoBroadcast.Number:type_name -> types_pb.H256 + 0, // 8: sync_proto.SyncTask.syncType:type_name -> sync_proto.SyncType + 5, // 9: sync_proto.SyncTask.syncHeaderRequest:type_name -> sync_proto.SyncHeaderRequest + 6, // 10: sync_proto.SyncTask.syncHeaderResponse:type_name -> sync_proto.SyncHeaderResponse + 3, // 11: sync_proto.SyncTask.syncBlockRequest:type_name -> sync_proto.SyncBlockRequest + 4, // 12: sync_proto.SyncTask.syncBlockResponse:type_name -> sync_proto.SyncBlockResponse + 7, // 13: sync_proto.SyncTask.syncTransactionRequest:type_name -> sync_proto.SyncTransactionRequest + 8, // 14: sync_proto.SyncTask.syncTransactionResponse:type_name -> sync_proto.SyncTransactionResponse + 9, // 15: sync_proto.SyncTask.syncPeerInfoBroadcast:type_name -> sync_proto.SyncPeerInfoBroadcast + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name +} + +func init() { file_sync_proto_init() } +func file_sync_proto_init() { + if File_sync_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_sync_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncProtocol); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Value); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncBlockRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncBlockResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncHeaderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncHeaderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncTransactionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncTransactionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncPeerInfoBroadcast); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sync_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncTask); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_sync_proto_msgTypes[9].OneofWrappers = []interface{}{ + (*SyncTask_SyncHeaderRequest)(nil), + (*SyncTask_SyncHeaderResponse)(nil), + (*SyncTask_SyncBlockRequest)(nil), + (*SyncTask_SyncBlockResponse)(nil), + (*SyncTask_SyncTransactionRequest)(nil), + (*SyncTask_SyncTransactionResponse)(nil), + (*SyncTask_SyncPeerInfoBroadcast)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_sync_proto_rawDesc, + NumEnums: 1, + NumMessages: 10, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_sync_proto_goTypes, + DependencyIndexes: file_sync_proto_depIdxs, + EnumInfos: file_sync_proto_enumTypes, + MessageInfos: file_sync_proto_msgTypes, + }.Build() + File_sync_proto = out.File + file_sync_proto_rawDesc = nil + file_sync_proto_goTypes = nil + file_sync_proto_depIdxs = nil +} diff --git a/api/protocol/sync_proto/sync.proto b/api/protocol/sync_proto/sync.proto new file mode 100644 index 0000000..5ad4f24 --- /dev/null +++ b/api/protocol/sync_proto/sync.proto @@ -0,0 +1,96 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package sync_proto; +option go_package = "github.com/astranetworld/ast/api/protocol/sync_proto"; + +import "types_pb/types.proto"; + +message SyncProtocol { + +} + + +enum SyncType { + FINDReq = 0; + FindRes = 1; + HeaderReq = 2; + HeaderRes = 3; + BodyReq = 4; + BodyRes = 5; + StateReq = 6; + StateRes = 7; + TransactionReq = 8; + TransactionRes = 9; + PeerInfoBroadcast = 10; +} + +message Value { + string hash = 1; + uint64 height = 2; +} + +message SyncBlockRequest { + repeated types_pb.H256 number = 1; +} + +message SyncBlockResponse { + repeated types_pb.Block blocks = 1; +} + +message SyncHeaderRequest { + types_pb.H256 number = 1; + types_pb.H256 amount = 3; +} + +message SyncHeaderResponse { + repeated types_pb.Header headers = 1; +} + +message SyncTransactionRequest { + bytes bloom = 1; +} + +message SyncTransactionResponse { + repeated types_pb.Transaction transactions = 1; +} + +message SyncPeerInfoBroadcast { + types_pb.H256 Difficulty = 1; + types_pb.H256 Number = 2; +} + + +message SyncTask { + uint64 id = 1; // task id + bool ok = 2; + SyncType syncType = 3; + oneof payload { + //header + SyncHeaderRequest syncHeaderRequest = 4; + SyncHeaderResponse syncHeaderResponse = 5; + //body + SyncBlockRequest syncBlockRequest = 6; + SyncBlockResponse syncBlockResponse = 7; + //Transaction + SyncTransactionRequest syncTransactionRequest = 8; + SyncTransactionResponse syncTransactionResponse = 9; + // + SyncPeerInfoBroadcast syncPeerInfoBroadcast = 10; + } +} + diff --git a/api/protocol/types_pb/gen.go b/api/protocol/types_pb/gen.go new file mode 100644 index 0000000..80d7607 --- /dev/null +++ b/api/protocol/types_pb/gen.go @@ -0,0 +1,21 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types_pb + +////go:generate protoc --plugin=/Users/mac/go/bin/protoc-gen-go-cast -I=../ -I=. -I=../include --go-cast_out=plugins=protoc-gen-go-cast,paths=source_relative:. types.proto +//go:generate protoc -I=../ -I=. -I=../include --go-cast_out=paths=source_relative:. types.proto +//go:generate sszgen -path=. -objs=H128,H160,H256,H384,H768,H512,H1024,H2048,Header,Body,Block,Transaction -output=generated.ssz.go diff --git a/api/protocol/types_pb/generated.ssz.go b/api/protocol/types_pb/generated.ssz.go new file mode 100644 index 0000000..02194fb --- /dev/null +++ b/api/protocol/types_pb/generated.ssz.go @@ -0,0 +1,1999 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: ff4c0802b1b743077781dae4de752322633988f240bb8e40a9f01dbceed1d869 +package types_pb + +import ( + ssz "github.com/prysmaticlabs/fastssz" +) + +// MarshalSSZ ssz marshals the H128 object +func (h *H128) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H128 object to a target array +func (h *H128) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + dst = ssz.MarshalUint64(dst, h.Hi) + + // Field (1) 'Lo' + dst = ssz.MarshalUint64(dst, h.Lo) + + return +} + +// UnmarshalSSZ ssz unmarshals the H128 object +func (h *H128) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 16 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + h.Hi = ssz.UnmarshallUint64(buf[0:8]) + + // Field (1) 'Lo' + h.Lo = ssz.UnmarshallUint64(buf[8:16]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H128 object +func (h *H128) SizeSSZ() (size int) { + size = 16 + return +} + +// HashTreeRoot ssz hashes the H128 object +func (h *H128) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H128 object with a hasher +func (h *H128) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + hh.PutUint64(h.Hi) + + // Field (1) 'Lo' + hh.PutUint64(h.Lo) + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H160 object +func (h *H160) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H160 object to a target array +func (h *H160) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H128) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + dst = ssz.MarshalUint32(dst, h.Lo) + + return +} + +// UnmarshalSSZ ssz unmarshals the H160 object +func (h *H160) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 20 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H128) + } + if err = h.Hi.UnmarshalSSZ(buf[0:16]); err != nil { + return err + } + + // Field (1) 'Lo' + h.Lo = ssz.UnmarshallUint32(buf[16:20]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H160 object +func (h *H160) SizeSSZ() (size int) { + size = 20 + return +} + +// HashTreeRoot ssz hashes the H160 object +func (h *H160) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H160 object with a hasher +func (h *H160) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + hh.PutUint32(h.Lo) + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H256 object +func (h *H256) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H256 object to a target array +func (h *H256) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H128) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H128) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H256 object +func (h *H256) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 32 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H128) + } + if err = h.Hi.UnmarshalSSZ(buf[0:16]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H128) + } + if err = h.Lo.UnmarshalSSZ(buf[16:32]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H256 object +func (h *H256) SizeSSZ() (size int) { + size = 32 + return +} + +// HashTreeRoot ssz hashes the H256 object +func (h *H256) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H256 object with a hasher +func (h *H256) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H384 object +func (h *H384) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H384 object to a target array +func (h *H384) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H256) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H128) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H384 object +func (h *H384) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 48 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H256) + } + if err = h.Hi.UnmarshalSSZ(buf[0:32]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H128) + } + if err = h.Lo.UnmarshalSSZ(buf[32:48]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H384 object +func (h *H384) SizeSSZ() (size int) { + size = 48 + return +} + +// HashTreeRoot ssz hashes the H384 object +func (h *H384) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H384 object with a hasher +func (h *H384) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H768 object +func (h *H768) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H768 object to a target array +func (h *H768) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H384) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H384) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H768 object +func (h *H768) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 96 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H384) + } + if err = h.Hi.UnmarshalSSZ(buf[0:48]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H384) + } + if err = h.Lo.UnmarshalSSZ(buf[48:96]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H768 object +func (h *H768) SizeSSZ() (size int) { + size = 96 + return +} + +// HashTreeRoot ssz hashes the H768 object +func (h *H768) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H768 object with a hasher +func (h *H768) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H512 object +func (h *H512) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H512 object to a target array +func (h *H512) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H256) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H256) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H512 object +func (h *H512) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 64 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H256) + } + if err = h.Hi.UnmarshalSSZ(buf[0:32]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H256) + } + if err = h.Lo.UnmarshalSSZ(buf[32:64]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H512 object +func (h *H512) SizeSSZ() (size int) { + size = 64 + return +} + +// HashTreeRoot ssz hashes the H512 object +func (h *H512) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H512 object with a hasher +func (h *H512) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H1024 object +func (h *H1024) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H1024 object to a target array +func (h *H1024) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H512) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H512) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H1024 object +func (h *H1024) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 128 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H512) + } + if err = h.Hi.UnmarshalSSZ(buf[0:64]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H512) + } + if err = h.Lo.UnmarshalSSZ(buf[64:128]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H1024 object +func (h *H1024) SizeSSZ() (size int) { + size = 128 + return +} + +// HashTreeRoot ssz hashes the H1024 object +func (h *H1024) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H1024 object with a hasher +func (h *H1024) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the H2048 object +func (h *H2048) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the H2048 object to a target array +func (h *H2048) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H1024) + } + if dst, err = h.Hi.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H1024) + } + if dst, err = h.Lo.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the H2048 object +func (h *H2048) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 256 { + return ssz.ErrSize + } + + // Field (0) 'Hi' + if h.Hi == nil { + h.Hi = new(H1024) + } + if err = h.Hi.UnmarshalSSZ(buf[0:128]); err != nil { + return err + } + + // Field (1) 'Lo' + if h.Lo == nil { + h.Lo = new(H1024) + } + if err = h.Lo.UnmarshalSSZ(buf[128:256]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the H2048 object +func (h *H2048) SizeSSZ() (size int) { + size = 256 + return +} + +// HashTreeRoot ssz hashes the H2048 object +func (h *H2048) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the H2048 object with a hasher +func (h *H2048) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Hi' + if err = h.Hi.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Lo' + if err = h.Lo.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Block object +func (b *Block) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the Block object to a target array +func (b *Block) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(8) + + // Offset (0) 'Header' + dst = ssz.WriteOffset(dst, offset) + if b.Header == nil { + b.Header = new(Header) + } + offset += b.Header.SizeSSZ() + + // Offset (1) 'Body' + dst = ssz.WriteOffset(dst, offset) + if b.Body == nil { + b.Body = new(Body) + } + offset += b.Body.SizeSSZ() + + // Field (0) 'Header' + if dst, err = b.Header.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Body' + if dst, err = b.Body.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Block object +func (b *Block) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 8 { + return ssz.ErrSize + } + + tail := buf + var o0, o1 uint64 + + // Offset (0) 'Header' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 8 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'Body' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Field (0) 'Header' + { + buf = tail[o0:o1] + if b.Header == nil { + b.Header = new(Header) + } + if err = b.Header.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (1) 'Body' + { + buf = tail[o1:] + if b.Body == nil { + b.Body = new(Body) + } + if err = b.Body.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Block object +func (b *Block) SizeSSZ() (size int) { + size = 8 + + // Field (0) 'Header' + if b.Header == nil { + b.Header = new(Header) + } + size += b.Header.SizeSSZ() + + // Field (1) 'Body' + if b.Body == nil { + b.Body = new(Body) + } + size += b.Body.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the Block object +func (b *Block) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the Block object with a hasher +func (b *Block) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Header' + if err = b.Header.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Body' + if err = b.Body.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Header object +func (h *Header) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(h) +} + +// MarshalSSZTo ssz marshals the Header object to a target array +func (h *Header) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(664) + + // Field (0) 'ParentHash' + if h.ParentHash == nil { + h.ParentHash = new(H256) + } + if dst, err = h.ParentHash.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Coinbase' + if h.Coinbase == nil { + h.Coinbase = new(H160) + } + if dst, err = h.Coinbase.MarshalSSZTo(dst); err != nil { + return + } + + // Field (2) 'Root' + if h.Root == nil { + h.Root = new(H256) + } + if dst, err = h.Root.MarshalSSZTo(dst); err != nil { + return + } + + // Field (3) 'TxHash' + if h.TxHash == nil { + h.TxHash = new(H256) + } + if dst, err = h.TxHash.MarshalSSZTo(dst); err != nil { + return + } + + // Field (4) 'ReceiptHash' + if h.ReceiptHash == nil { + h.ReceiptHash = new(H256) + } + if dst, err = h.ReceiptHash.MarshalSSZTo(dst); err != nil { + return + } + + // Field (5) 'Difficulty' + if h.Difficulty == nil { + h.Difficulty = new(H256) + } + if dst, err = h.Difficulty.MarshalSSZTo(dst); err != nil { + return + } + + // Field (6) 'Number' + if h.Number == nil { + h.Number = new(H256) + } + if dst, err = h.Number.MarshalSSZTo(dst); err != nil { + return + } + + // Field (7) 'GasLimit' + dst = ssz.MarshalUint64(dst, h.GasLimit) + + // Field (8) 'GasUsed' + dst = ssz.MarshalUint64(dst, h.GasUsed) + + // Field (9) 'Time' + dst = ssz.MarshalUint64(dst, h.Time) + + // Field (10) 'Nonce' + dst = ssz.MarshalUint64(dst, h.Nonce) + + // Field (11) 'BaseFee' + if h.BaseFee == nil { + h.BaseFee = new(H256) + } + if dst, err = h.BaseFee.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (12) 'Extra' + dst = ssz.WriteOffset(dst, offset) + offset += len(h.Extra) + + // Field (13) 'Signature' + if h.Signature == nil { + h.Signature = new(H768) + } + if dst, err = h.Signature.MarshalSSZTo(dst); err != nil { + return + } + + // Field (14) 'Bloom' + if h.Bloom == nil { + h.Bloom = new(H2048) + } + if dst, err = h.Bloom.MarshalSSZTo(dst); err != nil { + return + } + + // Field (15) 'MixDigest' + if h.MixDigest == nil { + h.MixDigest = new(H256) + } + if dst, err = h.MixDigest.MarshalSSZTo(dst); err != nil { + return + } + + // Field (12) 'Extra' + if size := len(h.Extra); size > 117 { + err = ssz.ErrBytesLengthFn("--.Extra", size, 117) + return + } + dst = append(dst, h.Extra...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Header object +func (h *Header) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 664 { + return ssz.ErrSize + } + + tail := buf + var o12 uint64 + + // Field (0) 'ParentHash' + if h.ParentHash == nil { + h.ParentHash = new(H256) + } + if err = h.ParentHash.UnmarshalSSZ(buf[0:32]); err != nil { + return err + } + + // Field (1) 'Coinbase' + if h.Coinbase == nil { + h.Coinbase = new(H160) + } + if err = h.Coinbase.UnmarshalSSZ(buf[32:52]); err != nil { + return err + } + + // Field (2) 'Root' + if h.Root == nil { + h.Root = new(H256) + } + if err = h.Root.UnmarshalSSZ(buf[52:84]); err != nil { + return err + } + + // Field (3) 'TxHash' + if h.TxHash == nil { + h.TxHash = new(H256) + } + if err = h.TxHash.UnmarshalSSZ(buf[84:116]); err != nil { + return err + } + + // Field (4) 'ReceiptHash' + if h.ReceiptHash == nil { + h.ReceiptHash = new(H256) + } + if err = h.ReceiptHash.UnmarshalSSZ(buf[116:148]); err != nil { + return err + } + + // Field (5) 'Difficulty' + if h.Difficulty == nil { + h.Difficulty = new(H256) + } + if err = h.Difficulty.UnmarshalSSZ(buf[148:180]); err != nil { + return err + } + + // Field (6) 'Number' + if h.Number == nil { + h.Number = new(H256) + } + if err = h.Number.UnmarshalSSZ(buf[180:212]); err != nil { + return err + } + + // Field (7) 'GasLimit' + h.GasLimit = ssz.UnmarshallUint64(buf[212:220]) + + // Field (8) 'GasUsed' + h.GasUsed = ssz.UnmarshallUint64(buf[220:228]) + + // Field (9) 'Time' + h.Time = ssz.UnmarshallUint64(buf[228:236]) + + // Field (10) 'Nonce' + h.Nonce = ssz.UnmarshallUint64(buf[236:244]) + + // Field (11) 'BaseFee' + if h.BaseFee == nil { + h.BaseFee = new(H256) + } + if err = h.BaseFee.UnmarshalSSZ(buf[244:276]); err != nil { + return err + } + + // Offset (12) 'Extra' + if o12 = ssz.ReadOffset(buf[276:280]); o12 > size { + return ssz.ErrOffset + } + + if o12 < 664 { + return ssz.ErrInvalidVariableOffset + } + + // Field (13) 'Signature' + if h.Signature == nil { + h.Signature = new(H768) + } + if err = h.Signature.UnmarshalSSZ(buf[280:376]); err != nil { + return err + } + + // Field (14) 'Bloom' + if h.Bloom == nil { + h.Bloom = new(H2048) + } + if err = h.Bloom.UnmarshalSSZ(buf[376:632]); err != nil { + return err + } + + // Field (15) 'MixDigest' + if h.MixDigest == nil { + h.MixDigest = new(H256) + } + if err = h.MixDigest.UnmarshalSSZ(buf[632:664]); err != nil { + return err + } + + // Field (12) 'Extra' + { + buf = tail[o12:] + if len(buf) > 117 { + return ssz.ErrBytesLength + } + if cap(h.Extra) == 0 { + h.Extra = make([]byte, 0, len(buf)) + } + h.Extra = append(h.Extra, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Header object +func (h *Header) SizeSSZ() (size int) { + size = 664 + + // Field (12) 'Extra' + size += len(h.Extra) + + return +} + +// HashTreeRoot ssz hashes the Header object +func (h *Header) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(h) +} + +// HashTreeRootWith ssz hashes the Header object with a hasher +func (h *Header) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'ParentHash' + if err = h.ParentHash.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Coinbase' + if err = h.Coinbase.HashTreeRootWith(hh); err != nil { + return + } + + // Field (2) 'Root' + if err = h.Root.HashTreeRootWith(hh); err != nil { + return + } + + // Field (3) 'TxHash' + if err = h.TxHash.HashTreeRootWith(hh); err != nil { + return + } + + // Field (4) 'ReceiptHash' + if err = h.ReceiptHash.HashTreeRootWith(hh); err != nil { + return + } + + // Field (5) 'Difficulty' + if err = h.Difficulty.HashTreeRootWith(hh); err != nil { + return + } + + // Field (6) 'Number' + if err = h.Number.HashTreeRootWith(hh); err != nil { + return + } + + // Field (7) 'GasLimit' + hh.PutUint64(h.GasLimit) + + // Field (8) 'GasUsed' + hh.PutUint64(h.GasUsed) + + // Field (9) 'Time' + hh.PutUint64(h.Time) + + // Field (10) 'Nonce' + hh.PutUint64(h.Nonce) + + // Field (11) 'BaseFee' + if err = h.BaseFee.HashTreeRootWith(hh); err != nil { + return + } + + // Field (12) 'Extra' + { + elemIndx := hh.Index() + byteLen := uint64(len(h.Extra)) + if byteLen > 117 { + err = ssz.ErrIncorrectListSize + return + } + hh.PutBytes(h.Extra) + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(elemIndx, byteLen, (117+31)/32) + } else { + hh.MerkleizeWithMixin(elemIndx, byteLen, (117+31)/32) + } + } + + // Field (13) 'Signature' + if err = h.Signature.HashTreeRootWith(hh); err != nil { + return + } + + // Field (14) 'Bloom' + if err = h.Bloom.HashTreeRootWith(hh); err != nil { + return + } + + // Field (15) 'MixDigest' + if err = h.MixDigest.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Verifier object +func (v *Verifier) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(v) +} + +// MarshalSSZTo ssz marshals the Verifier object to a target array +func (v *Verifier) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'PublicKey' + if v.PublicKey == nil { + v.PublicKey = new(H384) + } + if dst, err = v.PublicKey.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Address' + if v.Address == nil { + v.Address = new(H160) + } + if dst, err = v.Address.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Verifier object +func (v *Verifier) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 68 { + return ssz.ErrSize + } + + // Field (0) 'PublicKey' + if v.PublicKey == nil { + v.PublicKey = new(H384) + } + if err = v.PublicKey.UnmarshalSSZ(buf[0:48]); err != nil { + return err + } + + // Field (1) 'Address' + if v.Address == nil { + v.Address = new(H160) + } + if err = v.Address.UnmarshalSSZ(buf[48:68]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Verifier object +func (v *Verifier) SizeSSZ() (size int) { + size = 68 + return +} + +// HashTreeRoot ssz hashes the Verifier object +func (v *Verifier) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(v) +} + +// HashTreeRootWith ssz hashes the Verifier object with a hasher +func (v *Verifier) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'PublicKey' + if err = v.PublicKey.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Address' + if err = v.Address.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Reward object +func (r *Reward) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(r) +} + +// MarshalSSZTo ssz marshals the Reward object to a target array +func (r *Reward) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Amount' + if r.Amount == nil { + r.Amount = new(H256) + } + if dst, err = r.Amount.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Address' + if r.Address == nil { + r.Address = new(H160) + } + if dst, err = r.Address.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Reward object +func (r *Reward) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 52 { + return ssz.ErrSize + } + + // Field (0) 'Amount' + if r.Amount == nil { + r.Amount = new(H256) + } + if err = r.Amount.UnmarshalSSZ(buf[0:32]); err != nil { + return err + } + + // Field (1) 'Address' + if r.Address == nil { + r.Address = new(H160) + } + if err = r.Address.UnmarshalSSZ(buf[32:52]); err != nil { + return err + } + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Reward object +func (r *Reward) SizeSSZ() (size int) { + size = 52 + return +} + +// HashTreeRoot ssz hashes the Reward object +func (r *Reward) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(r) +} + +// HashTreeRootWith ssz hashes the Reward object with a hasher +func (r *Reward) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Amount' + if err = r.Amount.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Address' + if err = r.Address.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Body object +func (b *Body) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the Body object to a target array +func (b *Body) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Offset (0) 'Txs' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(b.Txs); ii++ { + offset += 4 + offset += b.Txs[ii].SizeSSZ() + } + + // Offset (1) 'Verifiers' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Verifiers) * 68 + + // Offset (2) 'Rewards' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Rewards) * 52 + + // Field (0) 'Txs' + if size := len(b.Txs); size > 104857600 { + err = ssz.ErrListTooBigFn("--.Txs", size, 104857600) + return + } + { + offset = 4 * len(b.Txs) + for ii := 0; ii < len(b.Txs); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += b.Txs[ii].SizeSSZ() + } + } + for ii := 0; ii < len(b.Txs); ii++ { + if dst, err = b.Txs[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (1) 'Verifiers' + if size := len(b.Verifiers); size > 104857600 { + err = ssz.ErrListTooBigFn("--.Verifiers", size, 104857600) + return + } + for ii := 0; ii < len(b.Verifiers); ii++ { + if dst, err = b.Verifiers[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (2) 'Rewards' + if size := len(b.Rewards); size > 104857600 { + err = ssz.ErrListTooBigFn("--.Rewards", size, 104857600) + return + } + for ii := 0; ii < len(b.Rewards); ii++ { + if dst, err = b.Rewards[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Body object +func (b *Body) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o0, o1, o2 uint64 + + // Offset (0) 'Txs' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 12 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'Verifiers' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Offset (2) 'Rewards' + if o2 = ssz.ReadOffset(buf[8:12]); o2 > size || o1 > o2 { + return ssz.ErrOffset + } + + // Field (0) 'Txs' + { + buf = tail[o0:o1] + num, err := ssz.DecodeDynamicLength(buf, 104857600) + if err != nil { + return err + } + b.Txs = make([]*Transaction, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if b.Txs[indx] == nil { + b.Txs[indx] = new(Transaction) + } + if err = b.Txs[indx].UnmarshalSSZ(buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + + // Field (1) 'Verifiers' + { + buf = tail[o1:o2] + num, err := ssz.DivideInt2(len(buf), 68, 104857600) + if err != nil { + return err + } + b.Verifiers = make([]*Verifier, num) + for ii := 0; ii < num; ii++ { + if b.Verifiers[ii] == nil { + b.Verifiers[ii] = new(Verifier) + } + if err = b.Verifiers[ii].UnmarshalSSZ(buf[ii*68 : (ii+1)*68]); err != nil { + return err + } + } + } + + // Field (2) 'Rewards' + { + buf = tail[o2:] + num, err := ssz.DivideInt2(len(buf), 52, 104857600) + if err != nil { + return err + } + b.Rewards = make([]*Reward, num) + for ii := 0; ii < num; ii++ { + if b.Rewards[ii] == nil { + b.Rewards[ii] = new(Reward) + } + if err = b.Rewards[ii].UnmarshalSSZ(buf[ii*52 : (ii+1)*52]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Body object +func (b *Body) SizeSSZ() (size int) { + size = 12 + + // Field (0) 'Txs' + for ii := 0; ii < len(b.Txs); ii++ { + size += 4 + size += b.Txs[ii].SizeSSZ() + } + + // Field (1) 'Verifiers' + size += len(b.Verifiers) * 68 + + // Field (2) 'Rewards' + size += len(b.Rewards) * 52 + + return +} + +// HashTreeRoot ssz hashes the Body object +func (b *Body) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the Body object with a hasher +func (b *Body) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Txs' + { + subIndx := hh.Index() + num := uint64(len(b.Txs)) + if num > 104857600 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Txs { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(subIndx, num, 104857600) + } else { + hh.MerkleizeWithMixin(subIndx, num, 104857600) + } + } + + // Field (1) 'Verifiers' + { + subIndx := hh.Index() + num := uint64(len(b.Verifiers)) + if num > 104857600 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Verifiers { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(subIndx, num, 104857600) + } else { + hh.MerkleizeWithMixin(subIndx, num, 104857600) + } + } + + // Field (2) 'Rewards' + { + subIndx := hh.Index() + num := uint64(len(b.Rewards)) + if num > 104857600 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Rewards { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(subIndx, num, 104857600) + } else { + hh.MerkleizeWithMixin(subIndx, num, 104857600) + } + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} + +// MarshalSSZ ssz marshals the Transaction object +func (t *Transaction) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(t) +} + +// MarshalSSZTo ssz marshals the Transaction object to a target array +func (t *Transaction) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(336) + + // Field (0) 'Type' + dst = ssz.MarshalUint64(dst, t.Type) + + // Field (1) 'Nonce' + dst = ssz.MarshalUint64(dst, t.Nonce) + + // Field (2) 'GasPrice' + if t.GasPrice == nil { + t.GasPrice = new(H256) + } + if dst, err = t.GasPrice.MarshalSSZTo(dst); err != nil { + return + } + + // Field (3) 'Gas' + dst = ssz.MarshalUint64(dst, t.Gas) + + // Field (4) 'FeePerGas' + if t.FeePerGas == nil { + t.FeePerGas = new(H256) + } + if dst, err = t.FeePerGas.MarshalSSZTo(dst); err != nil { + return + } + + // Field (5) 'PriorityFeePerGas' + if t.PriorityFeePerGas == nil { + t.PriorityFeePerGas = new(H256) + } + if dst, err = t.PriorityFeePerGas.MarshalSSZTo(dst); err != nil { + return + } + + // Field (6) 'Value' + if t.Value == nil { + t.Value = new(H256) + } + if dst, err = t.Value.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (7) 'Data' + dst = ssz.WriteOffset(dst, offset) + offset += len(t.Data) + + // Offset (8) 'Sign' + dst = ssz.WriteOffset(dst, offset) + offset += len(t.Sign) + + // Field (9) 'To' + if t.To == nil { + t.To = new(H160) + } + if dst, err = t.To.MarshalSSZTo(dst); err != nil { + return + } + + // Field (10) 'From' + if t.From == nil { + t.From = new(H160) + } + if dst, err = t.From.MarshalSSZTo(dst); err != nil { + return + } + + // Field (11) 'ChainID' + dst = ssz.MarshalUint64(dst, t.ChainID) + + // Field (12) 'Hash' + if t.Hash == nil { + t.Hash = new(H256) + } + if dst, err = t.Hash.MarshalSSZTo(dst); err != nil { + return + } + + // Field (13) 'R' + if t.R == nil { + t.R = new(H256) + } + if dst, err = t.R.MarshalSSZTo(dst); err != nil { + return + } + + // Field (14) 'S' + if t.S == nil { + t.S = new(H256) + } + if dst, err = t.S.MarshalSSZTo(dst); err != nil { + return + } + + // Field (15) 'V' + if t.V == nil { + t.V = new(H256) + } + if dst, err = t.V.MarshalSSZTo(dst); err != nil { + return + } + + // Field (7) 'Data' + if size := len(t.Data); size > 104857600 { + err = ssz.ErrBytesLengthFn("--.Data", size, 104857600) + return + } + dst = append(dst, t.Data...) + + // Field (8) 'Sign' + if size := len(t.Sign); size > 104857600 { + err = ssz.ErrBytesLengthFn("--.Sign", size, 104857600) + return + } + dst = append(dst, t.Sign...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Transaction object +func (t *Transaction) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 336 { + return ssz.ErrSize + } + + tail := buf + var o7, o8 uint64 + + // Field (0) 'Type' + t.Type = ssz.UnmarshallUint64(buf[0:8]) + + // Field (1) 'Nonce' + t.Nonce = ssz.UnmarshallUint64(buf[8:16]) + + // Field (2) 'GasPrice' + if t.GasPrice == nil { + t.GasPrice = new(H256) + } + if err = t.GasPrice.UnmarshalSSZ(buf[16:48]); err != nil { + return err + } + + // Field (3) 'Gas' + t.Gas = ssz.UnmarshallUint64(buf[48:56]) + + // Field (4) 'FeePerGas' + if t.FeePerGas == nil { + t.FeePerGas = new(H256) + } + if err = t.FeePerGas.UnmarshalSSZ(buf[56:88]); err != nil { + return err + } + + // Field (5) 'PriorityFeePerGas' + if t.PriorityFeePerGas == nil { + t.PriorityFeePerGas = new(H256) + } + if err = t.PriorityFeePerGas.UnmarshalSSZ(buf[88:120]); err != nil { + return err + } + + // Field (6) 'Value' + if t.Value == nil { + t.Value = new(H256) + } + if err = t.Value.UnmarshalSSZ(buf[120:152]); err != nil { + return err + } + + // Offset (7) 'Data' + if o7 = ssz.ReadOffset(buf[152:156]); o7 > size { + return ssz.ErrOffset + } + + if o7 < 336 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (8) 'Sign' + if o8 = ssz.ReadOffset(buf[156:160]); o8 > size || o7 > o8 { + return ssz.ErrOffset + } + + // Field (9) 'To' + if t.To == nil { + t.To = new(H160) + } + if err = t.To.UnmarshalSSZ(buf[160:180]); err != nil { + return err + } + + // Field (10) 'From' + if t.From == nil { + t.From = new(H160) + } + if err = t.From.UnmarshalSSZ(buf[180:200]); err != nil { + return err + } + + // Field (11) 'ChainID' + t.ChainID = ssz.UnmarshallUint64(buf[200:208]) + + // Field (12) 'Hash' + if t.Hash == nil { + t.Hash = new(H256) + } + if err = t.Hash.UnmarshalSSZ(buf[208:240]); err != nil { + return err + } + + // Field (13) 'R' + if t.R == nil { + t.R = new(H256) + } + if err = t.R.UnmarshalSSZ(buf[240:272]); err != nil { + return err + } + + // Field (14) 'S' + if t.S == nil { + t.S = new(H256) + } + if err = t.S.UnmarshalSSZ(buf[272:304]); err != nil { + return err + } + + // Field (15) 'V' + if t.V == nil { + t.V = new(H256) + } + if err = t.V.UnmarshalSSZ(buf[304:336]); err != nil { + return err + } + + // Field (7) 'Data' + { + buf = tail[o7:o8] + if len(buf) > 104857600 { + return ssz.ErrBytesLength + } + if cap(t.Data) == 0 { + t.Data = make([]byte, 0, len(buf)) + } + t.Data = append(t.Data, buf...) + } + + // Field (8) 'Sign' + { + buf = tail[o8:] + if len(buf) > 104857600 { + return ssz.ErrBytesLength + } + if cap(t.Sign) == 0 { + t.Sign = make([]byte, 0, len(buf)) + } + t.Sign = append(t.Sign, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Transaction object +func (t *Transaction) SizeSSZ() (size int) { + size = 336 + + // Field (7) 'Data' + size += len(t.Data) + + // Field (8) 'Sign' + size += len(t.Sign) + + return +} + +// HashTreeRoot ssz hashes the Transaction object +func (t *Transaction) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(t) +} + +// HashTreeRootWith ssz hashes the Transaction object with a hasher +func (t *Transaction) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Type' + hh.PutUint64(t.Type) + + // Field (1) 'Nonce' + hh.PutUint64(t.Nonce) + + // Field (2) 'GasPrice' + if err = t.GasPrice.HashTreeRootWith(hh); err != nil { + return + } + + // Field (3) 'Gas' + hh.PutUint64(t.Gas) + + // Field (4) 'FeePerGas' + if err = t.FeePerGas.HashTreeRootWith(hh); err != nil { + return + } + + // Field (5) 'PriorityFeePerGas' + if err = t.PriorityFeePerGas.HashTreeRootWith(hh); err != nil { + return + } + + // Field (6) 'Value' + if err = t.Value.HashTreeRootWith(hh); err != nil { + return + } + + // Field (7) 'Data' + { + elemIndx := hh.Index() + byteLen := uint64(len(t.Data)) + if byteLen > 104857600 { + err = ssz.ErrIncorrectListSize + return + } + hh.PutBytes(t.Data) + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(elemIndx, byteLen, (104857600+31)/32) + } else { + hh.MerkleizeWithMixin(elemIndx, byteLen, (104857600+31)/32) + } + } + + // Field (8) 'Sign' + { + elemIndx := hh.Index() + byteLen := uint64(len(t.Sign)) + if byteLen > 104857600 { + err = ssz.ErrIncorrectListSize + return + } + hh.PutBytes(t.Sign) + if ssz.EnableVectorizedHTR { + hh.MerkleizeWithMixinVectorizedHTR(elemIndx, byteLen, (104857600+31)/32) + } else { + hh.MerkleizeWithMixin(elemIndx, byteLen, (104857600+31)/32) + } + } + + // Field (9) 'To' + if err = t.To.HashTreeRootWith(hh); err != nil { + return + } + + // Field (10) 'From' + if err = t.From.HashTreeRootWith(hh); err != nil { + return + } + + // Field (11) 'ChainID' + hh.PutUint64(t.ChainID) + + // Field (12) 'Hash' + if err = t.Hash.HashTreeRootWith(hh); err != nil { + return + } + + // Field (13) 'R' + if err = t.R.HashTreeRootWith(hh); err != nil { + return + } + + // Field (14) 'S' + if err = t.S.HashTreeRootWith(hh); err != nil { + return + } + + // Field (15) 'V' + if err = t.V.HashTreeRootWith(hh); err != nil { + return + } + + if ssz.EnableVectorizedHTR { + hh.MerkleizeVectorizedHTR(indx) + } else { + hh.Merkleize(indx) + } + return +} diff --git a/api/protocol/types_pb/types.pb.go b/api/protocol/types_pb/types.pb.go new file mode 100644 index 0000000..96a6ff4 --- /dev/null +++ b/api/protocol/types_pb/types.pb.go @@ -0,0 +1,1924 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.20.0 +// source: types.proto + +package types_pb + +import ( + _ "github.com/astranetworld/ast/api/protocol/ext" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type H128 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi uint64 `protobuf:"varint,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo uint64 `protobuf:"varint,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H128) Reset() { + *x = H128{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H128) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H128) ProtoMessage() {} + +func (x *H128) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H128.ProtoReflect.Descriptor instead. +func (*H128) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{0} +} + +func (x *H128) GetHi() uint64 { + if x != nil { + return x.Hi + } + return 0 +} + +func (x *H128) GetLo() uint64 { + if x != nil { + return x.Lo + } + return 0 +} + +type H160 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H128 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo uint32 `protobuf:"varint,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H160) Reset() { + *x = H160{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H160) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H160) ProtoMessage() {} + +func (x *H160) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H160.ProtoReflect.Descriptor instead. +func (*H160) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{1} +} + +func (x *H160) GetHi() *H128 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H160) GetLo() uint32 { + if x != nil { + return x.Lo + } + return 0 +} + +type H256 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H128 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H128 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H256) Reset() { + *x = H256{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H256) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H256) ProtoMessage() {} + +func (x *H256) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H256.ProtoReflect.Descriptor instead. +func (*H256) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{2} +} + +func (x *H256) GetHi() *H128 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H256) GetLo() *H128 { + if x != nil { + return x.Lo + } + return nil +} + +type H384 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H256 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H128 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H384) Reset() { + *x = H384{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H384) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H384) ProtoMessage() {} + +func (x *H384) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H384.ProtoReflect.Descriptor instead. +func (*H384) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{3} +} + +func (x *H384) GetHi() *H256 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H384) GetLo() *H128 { + if x != nil { + return x.Lo + } + return nil +} + +type H768 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H384 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H384 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H768) Reset() { + *x = H768{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H768) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H768) ProtoMessage() {} + +func (x *H768) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H768.ProtoReflect.Descriptor instead. +func (*H768) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{4} +} + +func (x *H768) GetHi() *H384 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H768) GetLo() *H384 { + if x != nil { + return x.Lo + } + return nil +} + +type H512 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H256 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H256 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H512) Reset() { + *x = H512{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H512) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H512) ProtoMessage() {} + +func (x *H512) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H512.ProtoReflect.Descriptor instead. +func (*H512) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{5} +} + +func (x *H512) GetHi() *H256 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H512) GetLo() *H256 { + if x != nil { + return x.Lo + } + return nil +} + +type H1024 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H512 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H512 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H1024) Reset() { + *x = H1024{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H1024) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H1024) ProtoMessage() {} + +func (x *H1024) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H1024.ProtoReflect.Descriptor instead. +func (*H1024) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{6} +} + +func (x *H1024) GetHi() *H512 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H1024) GetLo() *H512 { + if x != nil { + return x.Lo + } + return nil +} + +type H2048 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hi *H1024 `protobuf:"bytes,1,opt,name=hi,proto3" json:"hi,omitempty"` + Lo *H1024 `protobuf:"bytes,2,opt,name=lo,proto3" json:"lo,omitempty"` +} + +func (x *H2048) Reset() { + *x = H2048{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *H2048) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H2048) ProtoMessage() {} + +func (x *H2048) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H2048.ProtoReflect.Descriptor instead. +func (*H2048) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{7} +} + +func (x *H2048) GetHi() *H1024 { + if x != nil { + return x.Hi + } + return nil +} + +func (x *H2048) GetLo() *H1024 { + if x != nil { + return x.Lo + } + return nil +} + +type Block struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *Header `protobuf:"bytes,1,opt,name=Header,proto3" json:"Header,omitempty"` + Body *Body `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *Block) Reset() { + *x = Block{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Block) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Block) ProtoMessage() {} + +func (x *Block) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Block.ProtoReflect.Descriptor instead. +func (*Block) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{8} +} + +func (x *Block) GetHeader() *Header { + if x != nil { + return x.Header + } + return nil +} + +func (x *Block) GetBody() *Body { + if x != nil { + return x.Body + } + return nil +} + +type Header struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentHash *H256 `protobuf:"bytes,1,opt,name=ParentHash,proto3" json:"ParentHash,omitempty"` + Coinbase *H160 `protobuf:"bytes,2,opt,name=Coinbase,proto3" json:"Coinbase,omitempty"` + Root *H256 `protobuf:"bytes,3,opt,name=Root,proto3" json:"Root,omitempty"` + TxHash *H256 `protobuf:"bytes,4,opt,name=TxHash,proto3" json:"TxHash,omitempty"` + ReceiptHash *H256 `protobuf:"bytes,5,opt,name=ReceiptHash,proto3" json:"ReceiptHash,omitempty"` + Difficulty *H256 `protobuf:"bytes,6,opt,name=Difficulty,proto3" json:"Difficulty,omitempty"` + Number *H256 `protobuf:"bytes,7,opt,name=Number,proto3" json:"Number,omitempty"` + GasLimit uint64 `protobuf:"varint,8,opt,name=GasLimit,proto3" json:"GasLimit,omitempty"` + GasUsed uint64 `protobuf:"varint,9,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"` + Time uint64 `protobuf:"varint,10,opt,name=Time,proto3" json:"Time,omitempty"` + Nonce uint64 `protobuf:"varint,11,opt,name=Nonce,proto3" json:"Nonce,omitempty"` + BaseFee *H256 `protobuf:"bytes,12,opt,name=BaseFee,proto3" json:"BaseFee,omitempty"` + // 65+32 byte (clique) + Extra []byte `protobuf:"bytes,13,opt,name=Extra,proto3" json:"Extra,omitempty" ssz-max:"117"` + Signature *H768 `protobuf:"bytes,14,opt,name=Signature,proto3" json:"Signature,omitempty"` + Bloom *H2048 `protobuf:"bytes,15,opt,name=Bloom,proto3" json:"Bloom,omitempty"` + MixDigest *H256 `protobuf:"bytes,16,opt,name=MixDigest,proto3" json:"MixDigest,omitempty"` +} + +func (x *Header) Reset() { + *x = Header{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Header) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Header) ProtoMessage() {} + +func (x *Header) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Header.ProtoReflect.Descriptor instead. +func (*Header) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{9} +} + +func (x *Header) GetParentHash() *H256 { + if x != nil { + return x.ParentHash + } + return nil +} + +func (x *Header) GetCoinbase() *H160 { + if x != nil { + return x.Coinbase + } + return nil +} + +func (x *Header) GetRoot() *H256 { + if x != nil { + return x.Root + } + return nil +} + +func (x *Header) GetTxHash() *H256 { + if x != nil { + return x.TxHash + } + return nil +} + +func (x *Header) GetReceiptHash() *H256 { + if x != nil { + return x.ReceiptHash + } + return nil +} + +func (x *Header) GetDifficulty() *H256 { + if x != nil { + return x.Difficulty + } + return nil +} + +func (x *Header) GetNumber() *H256 { + if x != nil { + return x.Number + } + return nil +} + +func (x *Header) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *Header) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *Header) GetTime() uint64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *Header) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *Header) GetBaseFee() *H256 { + if x != nil { + return x.BaseFee + } + return nil +} + +func (x *Header) GetExtra() []byte { + if x != nil { + return x.Extra + } + return nil +} + +func (x *Header) GetSignature() *H768 { + if x != nil { + return x.Signature + } + return nil +} + +func (x *Header) GetBloom() *H2048 { + if x != nil { + return x.Bloom + } + return nil +} + +func (x *Header) GetMixDigest() *H256 { + if x != nil { + return x.MixDigest + } + return nil +} + +type Verifier struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PublicKey *H384 `protobuf:"bytes,1,opt,name=PublicKey,proto3" json:"PublicKey,omitempty"` + Address *H160 `protobuf:"bytes,2,opt,name=Address,proto3" json:"Address,omitempty"` +} + +func (x *Verifier) Reset() { + *x = Verifier{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Verifier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Verifier) ProtoMessage() {} + +func (x *Verifier) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Verifier.ProtoReflect.Descriptor instead. +func (*Verifier) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{10} +} + +func (x *Verifier) GetPublicKey() *H384 { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *Verifier) GetAddress() *H160 { + if x != nil { + return x.Address + } + return nil +} + +type Reward struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount *H256 `protobuf:"bytes,1,opt,name=Amount,proto3" json:"Amount,omitempty"` + Address *H160 `protobuf:"bytes,2,opt,name=Address,proto3" json:"Address,omitempty"` +} + +func (x *Reward) Reset() { + *x = Reward{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Reward) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Reward) ProtoMessage() {} + +func (x *Reward) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Reward.ProtoReflect.Descriptor instead. +func (*Reward) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{11} +} + +func (x *Reward) GetAmount() *H256 { + if x != nil { + return x.Amount + } + return nil +} + +func (x *Reward) GetAddress() *H160 { + if x != nil { + return x.Address + } + return nil +} + +type Body struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Txs []*Transaction `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty" ssz-max:"104857600"` + Verifiers []*Verifier `protobuf:"bytes,2,rep,name=verifiers,proto3" json:"verifiers,omitempty" ssz-max:"104857600"` + Rewards []*Reward `protobuf:"bytes,3,rep,name=rewards,proto3" json:"rewards,omitempty" ssz-max:"104857600"` +} + +func (x *Body) Reset() { + *x = Body{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Body) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Body) ProtoMessage() {} + +func (x *Body) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Body.ProtoReflect.Descriptor instead. +func (*Body) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{12} +} + +func (x *Body) GetTxs() []*Transaction { + if x != nil { + return x.Txs + } + return nil +} + +func (x *Body) GetVerifiers() []*Verifier { + if x != nil { + return x.Verifiers + } + return nil +} + +func (x *Body) GetRewards() []*Reward { + if x != nil { + return x.Rewards + } + return nil +} + +type Transaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type uint64 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + GasPrice *H256 `protobuf:"bytes,3,opt,name=gasPrice,proto3" json:"gasPrice,omitempty"` + Gas uint64 `protobuf:"varint,4,opt,name=gas,proto3" json:"gas,omitempty"` + FeePerGas *H256 `protobuf:"bytes,5,opt,name=feePerGas,proto3" json:"feePerGas,omitempty"` + PriorityFeePerGas *H256 `protobuf:"bytes,6,opt,name=priorityFeePerGas,proto3" json:"priorityFeePerGas,omitempty"` + Value *H256 `protobuf:"bytes,7,opt,name=value,proto3" json:"value,omitempty"` + Data []byte `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty" ssz-max:"104857600"` + Sign []byte `protobuf:"bytes,9,opt,name=sign,proto3" json:"sign,omitempty" ssz-max:"104857600"` + To *H160 `protobuf:"bytes,10,opt,name=to,proto3" json:"to,omitempty"` + From *H160 `protobuf:"bytes,11,opt,name=from,proto3" json:"from,omitempty"` + ChainID uint64 `protobuf:"varint,12,opt,name=chainID,proto3" json:"chainID,omitempty"` + Hash *H256 `protobuf:"bytes,13,opt,name=hash,proto3" json:"hash,omitempty"` + R *H256 `protobuf:"bytes,14,opt,name=r,proto3" json:"r,omitempty"` + S *H256 `protobuf:"bytes,15,opt,name=s,proto3" json:"s,omitempty"` + V *H256 `protobuf:"bytes,16,opt,name=v,proto3" json:"v,omitempty"` +} + +func (x *Transaction) Reset() { + *x = Transaction{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction) ProtoMessage() {} + +func (x *Transaction) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction.ProtoReflect.Descriptor instead. +func (*Transaction) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{13} +} + +func (x *Transaction) GetType() uint64 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *Transaction) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *Transaction) GetGasPrice() *H256 { + if x != nil { + return x.GasPrice + } + return nil +} + +func (x *Transaction) GetGas() uint64 { + if x != nil { + return x.Gas + } + return 0 +} + +func (x *Transaction) GetFeePerGas() *H256 { + if x != nil { + return x.FeePerGas + } + return nil +} + +func (x *Transaction) GetPriorityFeePerGas() *H256 { + if x != nil { + return x.PriorityFeePerGas + } + return nil +} + +func (x *Transaction) GetValue() *H256 { + if x != nil { + return x.Value + } + return nil +} + +func (x *Transaction) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Transaction) GetSign() []byte { + if x != nil { + return x.Sign + } + return nil +} + +func (x *Transaction) GetTo() *H160 { + if x != nil { + return x.To + } + return nil +} + +func (x *Transaction) GetFrom() *H160 { + if x != nil { + return x.From + } + return nil +} + +func (x *Transaction) GetChainID() uint64 { + if x != nil { + return x.ChainID + } + return 0 +} + +func (x *Transaction) GetHash() *H256 { + if x != nil { + return x.Hash + } + return nil +} + +func (x *Transaction) GetR() *H256 { + if x != nil { + return x.R + } + return nil +} + +func (x *Transaction) GetS() *H256 { + if x != nil { + return x.S + } + return nil +} + +func (x *Transaction) GetV() *H256 { + if x != nil { + return x.V + } + return nil +} + +type Receipts struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Receipts []*Receipt `protobuf:"bytes,1,rep,name=receipts,proto3" json:"receipts,omitempty"` +} + +func (x *Receipts) Reset() { + *x = Receipts{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Receipts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Receipts) ProtoMessage() {} + +func (x *Receipts) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Receipts.ProtoReflect.Descriptor instead. +func (*Receipts) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{14} +} + +func (x *Receipts) GetReceipts() []*Receipt { + if x != nil { + return x.Receipts + } + return nil +} + +type Receipt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type uint32 `protobuf:"varint,1,opt,name=Type,proto3" json:"Type,omitempty"` + PostState []byte `protobuf:"bytes,2,opt,name=PostState,proto3" json:"PostState,omitempty"` + Status uint64 `protobuf:"varint,3,opt,name=Status,proto3" json:"Status,omitempty"` + CumulativeGasUsed uint64 `protobuf:"varint,4,opt,name=CumulativeGasUsed,proto3" json:"CumulativeGasUsed,omitempty"` + Bloom *H2048 `protobuf:"bytes,5,opt,name=Bloom,proto3" json:"Bloom,omitempty"` + Logs []*Log `protobuf:"bytes,6,rep,name=Logs,proto3" json:"Logs,omitempty"` + TxHash *H256 `protobuf:"bytes,7,opt,name=TxHash,proto3" json:"TxHash,omitempty"` + ContractAddress *H160 `protobuf:"bytes,8,opt,name=ContractAddress,proto3" json:"ContractAddress,omitempty"` + GasUsed uint64 `protobuf:"varint,9,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"` + BlockHash *H256 `protobuf:"bytes,10,opt,name=BlockHash,proto3" json:"BlockHash,omitempty"` + BlockNumber *H256 `protobuf:"bytes,11,opt,name=BlockNumber,proto3" json:"BlockNumber,omitempty"` + TransactionIndex uint64 `protobuf:"varint,12,opt,name=TransactionIndex,proto3" json:"TransactionIndex,omitempty"` +} + +func (x *Receipt) Reset() { + *x = Receipt{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Receipt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Receipt) ProtoMessage() {} + +func (x *Receipt) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Receipt.ProtoReflect.Descriptor instead. +func (*Receipt) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{15} +} + +func (x *Receipt) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *Receipt) GetPostState() []byte { + if x != nil { + return x.PostState + } + return nil +} + +func (x *Receipt) GetStatus() uint64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *Receipt) GetCumulativeGasUsed() uint64 { + if x != nil { + return x.CumulativeGasUsed + } + return 0 +} + +func (x *Receipt) GetBloom() *H2048 { + if x != nil { + return x.Bloom + } + return nil +} + +func (x *Receipt) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +func (x *Receipt) GetTxHash() *H256 { + if x != nil { + return x.TxHash + } + return nil +} + +func (x *Receipt) GetContractAddress() *H160 { + if x != nil { + return x.ContractAddress + } + return nil +} + +func (x *Receipt) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *Receipt) GetBlockHash() *H256 { + if x != nil { + return x.BlockHash + } + return nil +} + +func (x *Receipt) GetBlockNumber() *H256 { + if x != nil { + return x.BlockNumber + } + return nil +} + +func (x *Receipt) GetTransactionIndex() uint64 { + if x != nil { + return x.TransactionIndex + } + return 0 +} + +type Log struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *H160 `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` + Topics []*H256 `protobuf:"bytes,2,rep,name=Topics,proto3" json:"Topics,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=Data,proto3" json:"Data,omitempty"` + BlockNumber *H256 `protobuf:"bytes,4,opt,name=BlockNumber,proto3" json:"BlockNumber,omitempty"` + TxHash *H256 `protobuf:"bytes,5,opt,name=TxHash,proto3" json:"TxHash,omitempty"` + TxIndex uint64 `protobuf:"varint,6,opt,name=TxIndex,proto3" json:"TxIndex,omitempty"` + BlockHash *H256 `protobuf:"bytes,7,opt,name=BlockHash,proto3" json:"BlockHash,omitempty"` + Index uint64 `protobuf:"varint,8,opt,name=Index,proto3" json:"Index,omitempty"` + Removed bool `protobuf:"varint,9,opt,name=Removed,proto3" json:"Removed,omitempty"` +} + +func (x *Log) Reset() { + *x = Log{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{16} +} + +func (x *Log) GetAddress() *H160 { + if x != nil { + return x.Address + } + return nil +} + +func (x *Log) GetTopics() []*H256 { + if x != nil { + return x.Topics + } + return nil +} + +func (x *Log) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Log) GetBlockNumber() *H256 { + if x != nil { + return x.BlockNumber + } + return nil +} + +func (x *Log) GetTxHash() *H256 { + if x != nil { + return x.TxHash + } + return nil +} + +func (x *Log) GetTxIndex() uint64 { + if x != nil { + return x.TxIndex + } + return 0 +} + +func (x *Log) GetBlockHash() *H256 { + if x != nil { + return x.BlockHash + } + return nil +} + +func (x *Log) GetIndex() uint64 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Log) GetRemoved() bool { + if x != nil { + return x.Removed + } + return false +} + +type Logs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Logs []*Log `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` +} + +func (x *Logs) Reset() { + *x = Logs{} + if protoimpl.UnsafeEnabled { + mi := &file_types_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Logs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Logs) ProtoMessage() {} + +func (x *Logs) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Logs.ProtoReflect.Descriptor instead. +func (*Logs) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{17} +} + +func (x *Logs) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +var File_types_proto protoreflect.FileDescriptor + +var file_types_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x1a, 0x11, 0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x26, 0x0a, 0x04, 0x48, 0x31, + 0x32, 0x38, 0x12, 0x0e, 0x0a, 0x02, 0x68, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x68, 0x69, 0x12, 0x0e, 0x0a, 0x02, 0x6c, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x6c, 0x6f, 0x22, 0x36, 0x0a, 0x04, 0x48, 0x31, 0x36, 0x30, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x31, 0x32, 0x38, 0x52, 0x02, 0x68, 0x69, 0x12, 0x0e, 0x0a, 0x02, 0x6c, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x6c, 0x6f, 0x22, 0x46, 0x0a, 0x04, 0x48, 0x32, + 0x35, 0x36, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x32, 0x38, 0x52, 0x02, + 0x68, 0x69, 0x12, 0x1e, 0x0a, 0x02, 0x6c, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x32, 0x38, 0x52, 0x02, + 0x6c, 0x6f, 0x22, 0x46, 0x0a, 0x04, 0x48, 0x33, 0x38, 0x34, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x02, 0x68, 0x69, 0x12, 0x1e, 0x0a, 0x02, 0x6c, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x31, 0x32, 0x38, 0x52, 0x02, 0x6c, 0x6f, 0x22, 0x46, 0x0a, 0x04, 0x48, 0x37, + 0x36, 0x38, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x33, 0x38, 0x34, 0x52, 0x02, + 0x68, 0x69, 0x12, 0x1e, 0x0a, 0x02, 0x6c, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x33, 0x38, 0x34, 0x52, 0x02, + 0x6c, 0x6f, 0x22, 0x46, 0x0a, 0x04, 0x48, 0x35, 0x31, 0x32, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x02, 0x68, 0x69, 0x12, 0x1e, 0x0a, 0x02, 0x6c, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x02, 0x6c, 0x6f, 0x22, 0x47, 0x0a, 0x05, 0x48, 0x31, + 0x30, 0x32, 0x34, 0x12, 0x1e, 0x0a, 0x02, 0x68, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x35, 0x31, 0x32, 0x52, + 0x02, 0x68, 0x69, 0x12, 0x1e, 0x0a, 0x02, 0x6c, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x35, 0x31, 0x32, 0x52, + 0x02, 0x6c, 0x6f, 0x22, 0x49, 0x0a, 0x05, 0x48, 0x32, 0x30, 0x34, 0x38, 0x12, 0x1f, 0x0a, 0x02, + 0x68, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x30, 0x32, 0x34, 0x52, 0x02, 0x68, 0x69, 0x12, 0x1f, 0x0a, + 0x02, 0x6c, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x30, 0x32, 0x34, 0x52, 0x02, 0x6c, 0x6f, 0x22, 0x55, + 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x28, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x12, 0x22, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xe6, 0x04, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x12, 0x2e, 0x0a, 0x0a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x48, 0x32, 0x35, 0x36, 0x52, 0x0a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x2a, 0x0a, 0x08, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, + 0x36, 0x30, 0x52, 0x08, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x04, + 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x04, 0x52, 0x6f, 0x6f, 0x74, + 0x12, 0x26, 0x0a, 0x06, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, + 0x52, 0x06, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x30, 0x0a, 0x0b, 0x52, 0x65, 0x63, 0x65, + 0x69, 0x70, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0b, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x0a, 0x44, 0x69, + 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0a, + 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x06, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x4e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x47, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x47, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x69, 0x6d, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x4e, 0x6f, 0x6e, + 0x63, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, + 0x32, 0x35, 0x36, 0x52, 0x07, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x0a, 0x05, + 0x45, 0x78, 0x74, 0x72, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x07, 0x92, 0xb5, 0x18, + 0x03, 0x31, 0x31, 0x37, 0x52, 0x05, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x37, 0x36, 0x38, 0x52, 0x09, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x42, 0x6c, 0x6f, + 0x6f, 0x6d, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x30, 0x34, 0x38, 0x52, 0x05, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, + 0x12, 0x2c, 0x0a, 0x09, 0x4d, 0x69, 0x78, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, + 0x32, 0x35, 0x36, 0x52, 0x09, 0x4d, 0x69, 0x78, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x62, + 0x0a, 0x08, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x09, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x33, 0x38, 0x34, 0x52, 0x09, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, 0x30, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x22, 0x5a, 0x0a, 0x06, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x26, 0x0a, 0x06, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x31, 0x36, 0x30, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xba, + 0x01, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x36, 0x0a, 0x03, 0x74, 0x78, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0d, 0x92, 0xb5, 0x18, + 0x09, 0x31, 0x30, 0x34, 0x38, 0x35, 0x37, 0x36, 0x30, 0x30, 0x52, 0x03, 0x74, 0x78, 0x73, 0x12, + 0x3f, 0x0a, 0x09, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x30, 0x34, 0x38, + 0x35, 0x37, 0x36, 0x30, 0x30, 0x52, 0x09, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, + 0x12, 0x39, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x30, 0x34, 0x38, 0x35, 0x37, 0x36, + 0x30, 0x30, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0xa9, 0x04, 0x0a, 0x0b, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x08, 0x67, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, + 0x67, 0x61, 0x73, 0x12, 0x2c, 0x0a, 0x09, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x09, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, + 0x73, 0x12, 0x3c, 0x0a, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x46, 0x65, 0x65, + 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x11, 0x70, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, + 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0c, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x30, 0x34, 0x38, 0x35, 0x37, 0x36, + 0x30, 0x30, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x6e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x30, 0x34, 0x38, + 0x35, 0x37, 0x36, 0x30, 0x30, 0x52, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x1e, 0x0a, 0x02, 0x74, + 0x6f, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, 0x30, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x22, 0x0a, 0x04, 0x66, + 0x72, 0x6f, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, 0x30, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, + 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x44, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, + 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, + 0x01, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x01, 0x72, 0x12, 0x1c, 0x0a, 0x01, 0x73, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, + 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x01, 0x73, 0x12, 0x1c, 0x0a, 0x01, 0x76, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, + 0x48, 0x32, 0x35, 0x36, 0x52, 0x01, 0x76, 0x22, 0x39, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x70, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, + 0x74, 0x73, 0x22, 0xd3, 0x03, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x43, 0x75, 0x6d, 0x75, + 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x43, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x47, + 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x32, 0x30, 0x34, 0x38, 0x52, 0x05, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x12, 0x21, 0x0a, + 0x04, 0x4c, 0x6f, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x4c, 0x6f, 0x67, 0x73, + 0x12, 0x26, 0x0a, 0x06, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, + 0x52, 0x06, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x38, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, + 0x30, 0x52, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x07, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x09, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, + 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x30, 0x0a, 0x0b, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, + 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x10, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xbd, 0x02, 0x0a, 0x03, 0x4c, 0x6f, 0x67, + 0x12, 0x28, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x31, 0x36, + 0x30, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, 0x06, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x54, 0x6f, 0x70, 0x69, + 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x0b, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x54, 0x78, 0x48, 0x61, + 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x06, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x18, 0x0a, 0x07, 0x54, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x54, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2c, 0x0a, 0x09, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x32, 0x35, 0x36, 0x52, 0x09, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, + 0x0a, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x04, 0x4c, 0x6f, 0x67, 0x73, + 0x12, 0x21, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, + 0x6f, 0x67, 0x73, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x6d, 0x61, 0x7a, 0x65, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x61, 0x6d, 0x63, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_proto_rawDescOnce sync.Once + file_types_proto_rawDescData = file_types_proto_rawDesc +) + +func file_types_proto_rawDescGZIP() []byte { + file_types_proto_rawDescOnce.Do(func() { + file_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_proto_rawDescData) + }) + return file_types_proto_rawDescData +} + +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_types_proto_goTypes = []interface{}{ + (*H128)(nil), // 0: types_pb.H128 + (*H160)(nil), // 1: types_pb.H160 + (*H256)(nil), // 2: types_pb.H256 + (*H384)(nil), // 3: types_pb.H384 + (*H768)(nil), // 4: types_pb.H768 + (*H512)(nil), // 5: types_pb.H512 + (*H1024)(nil), // 6: types_pb.H1024 + (*H2048)(nil), // 7: types_pb.H2048 + (*Block)(nil), // 8: types_pb.Block + (*Header)(nil), // 9: types_pb.Header + (*Verifier)(nil), // 10: types_pb.Verifier + (*Reward)(nil), // 11: types_pb.Reward + (*Body)(nil), // 12: types_pb.Body + (*Transaction)(nil), // 13: types_pb.Transaction + (*Receipts)(nil), // 14: types_pb.Receipts + (*Receipt)(nil), // 15: types_pb.Receipt + (*Log)(nil), // 16: types_pb.Log + (*Logs)(nil), // 17: types_pb.Logs +} +var file_types_proto_depIdxs = []int32{ + 0, // 0: types_pb.H160.hi:type_name -> types_pb.H128 + 0, // 1: types_pb.H256.hi:type_name -> types_pb.H128 + 0, // 2: types_pb.H256.lo:type_name -> types_pb.H128 + 2, // 3: types_pb.H384.hi:type_name -> types_pb.H256 + 0, // 4: types_pb.H384.lo:type_name -> types_pb.H128 + 3, // 5: types_pb.H768.hi:type_name -> types_pb.H384 + 3, // 6: types_pb.H768.lo:type_name -> types_pb.H384 + 2, // 7: types_pb.H512.hi:type_name -> types_pb.H256 + 2, // 8: types_pb.H512.lo:type_name -> types_pb.H256 + 5, // 9: types_pb.H1024.hi:type_name -> types_pb.H512 + 5, // 10: types_pb.H1024.lo:type_name -> types_pb.H512 + 6, // 11: types_pb.H2048.hi:type_name -> types_pb.H1024 + 6, // 12: types_pb.H2048.lo:type_name -> types_pb.H1024 + 9, // 13: types_pb.Block.Header:type_name -> types_pb.Header + 12, // 14: types_pb.Block.body:type_name -> types_pb.Body + 2, // 15: types_pb.Header.ParentHash:type_name -> types_pb.H256 + 1, // 16: types_pb.Header.Coinbase:type_name -> types_pb.H160 + 2, // 17: types_pb.Header.Root:type_name -> types_pb.H256 + 2, // 18: types_pb.Header.TxHash:type_name -> types_pb.H256 + 2, // 19: types_pb.Header.ReceiptHash:type_name -> types_pb.H256 + 2, // 20: types_pb.Header.Difficulty:type_name -> types_pb.H256 + 2, // 21: types_pb.Header.Number:type_name -> types_pb.H256 + 2, // 22: types_pb.Header.BaseFee:type_name -> types_pb.H256 + 4, // 23: types_pb.Header.Signature:type_name -> types_pb.H768 + 7, // 24: types_pb.Header.Bloom:type_name -> types_pb.H2048 + 2, // 25: types_pb.Header.MixDigest:type_name -> types_pb.H256 + 3, // 26: types_pb.Verifier.PublicKey:type_name -> types_pb.H384 + 1, // 27: types_pb.Verifier.Address:type_name -> types_pb.H160 + 2, // 28: types_pb.Reward.Amount:type_name -> types_pb.H256 + 1, // 29: types_pb.Reward.Address:type_name -> types_pb.H160 + 13, // 30: types_pb.Body.txs:type_name -> types_pb.Transaction + 10, // 31: types_pb.Body.verifiers:type_name -> types_pb.Verifier + 11, // 32: types_pb.Body.rewards:type_name -> types_pb.Reward + 2, // 33: types_pb.Transaction.gasPrice:type_name -> types_pb.H256 + 2, // 34: types_pb.Transaction.feePerGas:type_name -> types_pb.H256 + 2, // 35: types_pb.Transaction.priorityFeePerGas:type_name -> types_pb.H256 + 2, // 36: types_pb.Transaction.value:type_name -> types_pb.H256 + 1, // 37: types_pb.Transaction.to:type_name -> types_pb.H160 + 1, // 38: types_pb.Transaction.from:type_name -> types_pb.H160 + 2, // 39: types_pb.Transaction.hash:type_name -> types_pb.H256 + 2, // 40: types_pb.Transaction.r:type_name -> types_pb.H256 + 2, // 41: types_pb.Transaction.s:type_name -> types_pb.H256 + 2, // 42: types_pb.Transaction.v:type_name -> types_pb.H256 + 15, // 43: types_pb.Receipts.receipts:type_name -> types_pb.Receipt + 7, // 44: types_pb.Receipt.Bloom:type_name -> types_pb.H2048 + 16, // 45: types_pb.Receipt.Logs:type_name -> types_pb.Log + 2, // 46: types_pb.Receipt.TxHash:type_name -> types_pb.H256 + 1, // 47: types_pb.Receipt.ContractAddress:type_name -> types_pb.H160 + 2, // 48: types_pb.Receipt.BlockHash:type_name -> types_pb.H256 + 2, // 49: types_pb.Receipt.BlockNumber:type_name -> types_pb.H256 + 1, // 50: types_pb.Log.Address:type_name -> types_pb.H160 + 2, // 51: types_pb.Log.Topics:type_name -> types_pb.H256 + 2, // 52: types_pb.Log.BlockNumber:type_name -> types_pb.H256 + 2, // 53: types_pb.Log.TxHash:type_name -> types_pb.H256 + 2, // 54: types_pb.Log.BlockHash:type_name -> types_pb.H256 + 16, // 55: types_pb.Logs.logs:type_name -> types_pb.Log + 56, // [56:56] is the sub-list for method output_type + 56, // [56:56] is the sub-list for method input_type + 56, // [56:56] is the sub-list for extension type_name + 56, // [56:56] is the sub-list for extension extendee + 0, // [0:56] is the sub-list for field type_name +} + +func init() { file_types_proto_init() } +func file_types_proto_init() { + if File_types_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H128); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H160); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H256); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H384); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H768); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H512); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H1024); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*H2048); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Block); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Header); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Verifier); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Reward); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Body); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Receipts); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Receipt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Log); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Logs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_proto_rawDesc, + NumEnums: 0, + NumMessages: 18, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_proto_goTypes, + DependencyIndexes: file_types_proto_depIdxs, + MessageInfos: file_types_proto_msgTypes, + }.Build() + File_types_proto = out.File + file_types_proto_rawDesc = nil + file_types_proto_goTypes = nil + file_types_proto_depIdxs = nil +} diff --git a/api/protocol/types_pb/types.proto b/api/protocol/types_pb/types.proto new file mode 100644 index 0000000..e77bf9e --- /dev/null +++ b/api/protocol/types_pb/types.proto @@ -0,0 +1,160 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +syntax = "proto3"; +package types_pb; + +import "ext/options.proto"; + +option go_package = "github.com/astranetworld/ast/api/protocol/types_pb"; + + +message H128 { + uint64 hi = 1; + uint64 lo = 2; +} + +message H160 { + H128 hi = 1; + uint32 lo = 2; +} + +message H256 { + H128 hi = 1; + H128 lo = 2; +} + +message H384{ // 48 byte + H256 hi = 1; + H128 lo = 2; +} + +message H768 { // 96 byte + H384 hi = 1; + H384 lo = 2; +} + +message H512 { + H256 hi = 1; + H256 lo = 2; +} + +message H1024 { + H512 hi = 1; + H512 lo = 2; +} + +message H2048 { + H1024 hi = 1; + H1024 lo = 2; +} + + +message Block{ + Header Header = 1; + Body body = 2; +} + +message Header { + H256 ParentHash = 1 ; + H160 Coinbase = 2 ; + H256 Root = 3 ; + H256 TxHash = 4 ; + H256 ReceiptHash = 5 ; + H256 Difficulty = 6; + H256 Number = 7; + uint64 GasLimit = 8; + uint64 GasUsed = 9; + uint64 Time = 10; + uint64 Nonce = 11; + H256 BaseFee = 12 ; + // 65+32 byte (clique) + bytes Extra = 13 [(ext.ssz_max) = "117"]; + H768 Signature = 14; + H2048 Bloom = 15; + H256 MixDigest = 16; +} + +message Verifier { + H384 PublicKey = 1; + H160 Address = 2; +} + +message Reward { + H256 Amount = 1; + H160 Address = 2; +} + +message Body { + repeated Transaction txs = 1 [(ext.ssz_max) = "104857600"]; + repeated Verifier verifiers = 2 [(ext.ssz_max) = "104857600"]; + repeated Reward rewards = 3 [(ext.ssz_max) = "104857600"]; +} + +message Transaction { + uint64 type = 1; + uint64 nonce = 2; + H256 gasPrice = 3; + uint64 gas = 4; + H256 feePerGas = 5; + H256 priorityFeePerGas = 6; + H256 value = 7; + bytes data = 8 [(ext.ssz_max) = "104857600"]; + bytes sign = 9 [(ext.ssz_max) = "104857600"]; + H160 to = 10; + H160 from = 11; + uint64 chainID = 12; + H256 hash = 13; + H256 r = 14; + H256 s = 15; + H256 v = 16; +} + +message Receipts { + repeated Receipt receipts = 1; +} + +message Receipt { + uint32 Type = 1; + bytes PostState = 2; + uint64 Status = 3; + uint64 CumulativeGasUsed = 4; + H2048 Bloom = 5; + repeated Log Logs = 6; + H256 TxHash = 7 ; + H160 ContractAddress = 8; + uint64 GasUsed = 9; + H256 BlockHash = 10; + H256 BlockNumber = 11; + uint64 TransactionIndex = 12; +} + +message Log { + H160 Address = 1; + repeated H256 Topics = 2; + bytes Data = 3; + H256 BlockNumber = 4; + H256 TxHash = 5; + uint64 TxIndex = 6; + H256 BlockHash = 7; + uint64 Index = 8; + bool Removed = 9; +} + +message Logs { + repeated Log logs = 1; +} + diff --git a/cmd/ast/accountcmd.go b/cmd/ast/accountcmd.go new file mode 100644 index 0000000..9ff309a --- /dev/null +++ b/cmd/ast/accountcmd.go @@ -0,0 +1,386 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "fmt" + "github.com/astranetworld/ast/log" + "github.com/urfave/cli/v2" + "os" + + "github.com/astranetworld/ast/cmd/utils" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/accounts/keystore" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/conf" + "github.com/astranetworld/ast/internal/node" +) + +var ( + walletCommand = &cli.Command{ + Name: "wallet", + Usage: "Manage astranet presale wallets", + ArgsUsage: "", + Description: ` + astranet wallet import /path/to/my/presale.wallet + +will prompt for your password and imports your ether presale account. +It can be used non-interactively with the --password option taking a +passwordfile as argument containing the wallet password in plaintext.`, + Subcommands: []*cli.Command{ + { + + Name: "import", + Usage: "Import Ethereum presale wallet", + ArgsUsage: "", + Action: importWallet, + Flags: []cli.Flag{ + DataDirFlag, + KeyStoreDirFlag, + PasswordFileFlag, + LightKDFFlag, + }, + Description: ` + astranet wallet [options] /path/to/my/presale.wallet + +will prompt for your password and imports your ether presale account. +It can be used non-interactively with the --password option taking a +passwordfile as argument containing the wallet password in plaintext.`, + }, + }, + } + + accountCommand = &cli.Command{ + Name: "account", + Usage: "Manage accounts", + Description: ` + +Manage accounts, list all existing accounts, import a private key into a new +account, create a new account or update an existing account. + +It supports interactive mode, when you are prompted for password as well as +non-interactive mode where passwords are supplied via a given password file. +Non-interactive mode is only meant for scripted use on test networks or known +safe environments. + +Make sure you remember the password you gave when creating a new account (with +either new or import). Without it you are not able to unlock your account. + +Note that exporting your key in unencrypted format is NOT supported. + +Keys are stored under /keystore. +It is safe to transfer the entire directory or the individual keys therein +between ethereum nodes by simply copying. + +Make sure you backup your keys regularly.`, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "Print summary of existing accounts", + Action: accountList, + Flags: []cli.Flag{ + DataDirFlag, + KeyStoreDirFlag, + }, + Description: ` + Print a short summary of all accounts`, + }, + { + Name: "new", + Usage: "Create a new account", + Action: accountCreate, + Flags: []cli.Flag{ + DataDirFlag, + KeyStoreDirFlag, + PasswordFileFlag, + LightKDFFlag, + }, + Description: ` + astranet account new + +Creates a new account and prints the address. + +The account is saved in encrypted format, you are prompted for a password. + +You must remember this password to unlock your account in the future. + +For non-interactive use the password can be specified with the --password flag: + +Note, this is meant to be used for testing only, it is a bad idea to save your +password to file or expose in any other way. +`, + }, + { + Name: "update", + Usage: "Update an existing account", + Action: accountUpdate, + ArgsUsage: "
", + Flags: []cli.Flag{ + DataDirFlag, + KeyStoreDirFlag, + LightKDFFlag, + }, + Description: ` + astranet account update
+ +Update an existing account. + +The account is saved in the newest version in encrypted format, you are prompted +for a password to unlock the account and another to save the updated file. + +This same command can therefore be used to migrate an account of a deprecated +format to the newest format or change the password for an account. + +For non-interactive use the password can be specified with the --password flag: + + astranet account update [options]
+ +Since only one password can be given, only format update can be performed, +changing your password is only possible interactively. +`, + }, + { + Name: "import", + Usage: "Import a private key into a new account", + Action: accountImport, + Flags: []cli.Flag{ + DataDirFlag, + KeyStoreDirFlag, + PasswordFileFlag, + LightKDFFlag, + }, + ArgsUsage: "", + Description: ` + astranet account import + +Imports an unencrypted private key from and creates a new account. +Prints the address. + +The keyfile is assumed to contain an unencrypted private key in hexadecimal format. + +The account is saved in encrypted format, you are prompted for a password. + +You must remember this password to unlock your account in the future. + +For non-interactive use the password can be specified with the -password flag: + + astranet account import [options] + +Note: +As you can directly copy your encrypted accounts to another ethereum instance, +this import mechanism is not needed when you transfer an account between +nodes. +`, + }, + }, + } +) + +func importWallet(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("keyfile must be given as the only argument") + } + keyfile := ctx.Args().First() + keyJSON, err := os.ReadFile(keyfile) + if err != nil { + utils.Fatalf("Could not read wallet file: %v", err) + } + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + + passphrase := utils.GetPassPhraseWithList("", false, 0, MakePasswordList(ctx)) + + ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) + if err != nil { + utils.Fatalf("%v", err) + } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil +} + +func accountList(ctx *cli.Context) error { + cfg := DefaultConfig + // Load config file. + if len(cfgFile) > 0 { + if err := conf.LoadConfigFromFile(cfgFile, &cfg); err != nil { + utils.Fatalf("%v", err) + } + } + + stack, err := node.NewNode(ctx, &cfg) + if err != nil { + return err + } + + var index int + for _, wallet := range stack.AccountManager().Wallets() { + for _, account := range wallet.Accounts() { + fmt.Printf("Account #%d: {%s} %s\n", index, account.Address, &account.URL) + index++ + } + } + return nil +} + +// accountCreate creates a new account into the keystore defined by the CLI flags. +func accountCreate(ctx *cli.Context) error { + cfg := DefaultConfig + // Load config file. + if len(cfgFile) > 0 { + if err := conf.LoadConfigFromFile(cfgFile, &cfg); err != nil { + utils.Fatalf("%v", err) + } + } + + keydir, err := cfg.NodeCfg.KeyDirConfig() + if err != nil { + utils.Fatalf("Failed to read configuration: %v", err) + } + scryptN := keystore.StandardScryptN + scryptP := keystore.StandardScryptP + if cfg.NodeCfg.UseLightweightKDF { + scryptN = keystore.LightScryptN + scryptP = keystore.LightScryptP + } + + password := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx)) + + account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) + + if err != nil { + utils.Fatalf("Failed to create account: %v", err) + } + fmt.Printf("\nYour new key was generated\n\n") + fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) + fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) + fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") + fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") + fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") + fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") + return nil +} + +// tries unlocking the specified account a few times. +func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { + account, err := utils.MakeAddress(ks, address) + if err != nil { + utils.Fatalf("Could not list accounts: %v", err) + } + for trials := 0; trials < 3; trials++ { + prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) + password := utils.GetPassPhraseWithList(prompt, false, i, passwords) + err = ks.Unlock(account, password) + if err == nil { + log.Info("Unlocked account", "address", account.Address.Hex()) + return account, password + } + if err, ok := err.(*keystore.AmbiguousAddrError); ok { + log.Info("Unlocked account", "address", account.Address.Hex()) + return ambiguousAddrRecovery(ks, err, password), password + } + if err != keystore.ErrDecrypt { + // No need to prompt again if the error is not decryption-related. + break + } + } + // All trials expended to unlock account, bail out + utils.Fatalf("Failed to unlock account %s (%v)", address, err) + + return accounts.Account{}, "" +} + +func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { + fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) + for _, a := range err.Matches { + fmt.Println(" ", a.URL) + } + fmt.Println("Testing your password against all of them...") + var match *accounts.Account + for i, a := range err.Matches { + if e := ks.Unlock(a, auth); e == nil { + match = &err.Matches[i] + break + } + } + if match == nil { + utils.Fatalf("None of the listed files could be unlocked.") + return accounts.Account{} + } + fmt.Printf("Your password unlocked %s\n", match.URL) + fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") + for _, a := range err.Matches { + if a != *match { + fmt.Println(" ", a.URL) + } + } + return *match +} + +// accountUpdate transitions an account from a previous format to the current +// one, also providing the possibility to change the pass-phrase. +func accountUpdate(ctx *cli.Context) error { + if ctx.Args().Len() == 0 { + utils.Fatalf("No accounts specified to update") + } + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + + ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + + for _, addr := range ctx.Args().Slice() { + account, oldPassword := unlockAccount(ks, addr, 0, nil) + newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) + if err := ks.Update(account, oldPassword, newPassword); err != nil { + utils.Fatalf("Could not update the account: %v", err) + } + } + return nil +} + +func accountImport(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("keyfile must be given as the only argument") + } + keyfile := ctx.Args().First() + key, err := crypto.LoadECDSA(keyfile) + if err != nil { + utils.Fatalf("Failed to load the private key: %v", err) + } + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + + passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx)) + + ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + acct, err := ks.ImportECDSA(key, passphrase) + if err != nil { + utils.Fatalf("Could not create the account: %v", err) + } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil +} diff --git a/cmd/ast/app.go b/cmd/ast/app.go new file mode 100644 index 0000000..2bfbde1 --- /dev/null +++ b/cmd/ast/app.go @@ -0,0 +1,242 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "fmt" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "github.com/urfave/cli/v2" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + "runtime" + "strings" + "syscall" + "time" + + "github.com/astranetworld/ast/accounts" + + "github.com/astranetworld/ast/accounts/keystore" + "github.com/astranetworld/ast/cmd/utils" + + "github.com/astranetworld/ast/conf" + "github.com/astranetworld/ast/internal/node" +) + +func appRun(ctx *cli.Context) error { + if len(cfgFile) > 0 { + if err := conf.LoadConfigFromFile(cfgFile, &DefaultConfig); err != nil { + return err + } + } else { + DefaultConfig.NetworkCfg.ListenersAddress = listenAddress.Value() + DefaultConfig.NetworkCfg.BootstrapPeers = bootstraps.Value() + if len(privateKey) > 0 { + DefaultConfig.NetworkCfg.LocalPeerKey = privateKey + } + + DefaultConfig.P2PCfg.StaticPeers = p2pStaticPeers.Value() + DefaultConfig.P2PCfg.BootstrapNodeAddr = p2pBootstrapNode.Value() + DefaultConfig.P2PCfg.DenyListCIDR = p2pDenyList.Value() + + // + DefaultConfig.P2PCfg.DataDir = DefaultConfig.NodeCfg.DataDir + } + + log.Init(DefaultConfig.NodeCfg, DefaultConfig.LoggerCfg) + + if DefaultConfig.PprofCfg.Pprof { + if DefaultConfig.PprofCfg.MaxCpu > 0 { + runtime.GOMAXPROCS(DefaultConfig.PprofCfg.MaxCpu) + } + if DefaultConfig.PprofCfg.TraceMutex { + runtime.SetMutexProfileFraction(1) + } + if DefaultConfig.PprofCfg.TraceBlock { + runtime.SetBlockProfileRate(1) + } + + go func() { + if err := http.ListenAndServe(fmt.Sprintf(":%d", DefaultConfig.PprofCfg.Port), nil); err != nil { + log.Error("failed to setup go pprof", "err", err) + os.Exit(0) + } + }() + } + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + log.Error("Failed start Node", "err", err) + return err + } + + StartNode(ctx, stack, false) + + // Unlock any account specifically requested + unlockAccounts(ctx, stack, &DefaultConfig) + + // Register wallet event handlers to open and auto-derive wallets + events := make(chan accounts.WalletEvent, 16) + stack.AccountManager().Subscribe(events) + + go func() { + // Open any wallets already attached + for _, wallet := range stack.AccountManager().Wallets() { + if err := wallet.Open(""); err != nil { + log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) + } + } + // Listen for wallet event till termination + for event := range events { + switch event.Kind { + case accounts.WalletArrived: + if err := event.Wallet.Open(""); err != nil { + log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) + } + case accounts.WalletOpened: + status, _ := event.Wallet.Status() + log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) + + var derivationPaths []accounts.DerivationPath + if event.Wallet.URL().Scheme == "ledger" { + derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) + } + derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath) + + event.Wallet.SelfDerive(derivationPaths, nil) + + case accounts.WalletDropped: + log.Info("Old wallet dropped", "url", event.Wallet.URL()) + event.Wallet.Close() + } + } + }() + + stack.Wait() + + return nil +} + +// unlockAccounts unlocks any account specifically requested. +func unlockAccounts(ctx *cli.Context, stack *node.Node, cfg *conf.Config) { + var unlocks []string + inputs := strings.Split(ctx.String(UnlockedAccountFlag.Name), ",") + for _, input := range inputs { + if trimmed := strings.TrimSpace(input); trimmed != "" { + unlocks = append(unlocks, trimmed) + } + } + + // Short circuit if there is no account to unlock. + if len(unlocks) == 0 { + return + } + // If insecure account unlocking is not allowed if node's APIs are exposed to external. + // Print warning log to user and skip unlocking. + if !cfg.NodeCfg.InsecureUnlockAllowed && cfg.NodeCfg.ExtRPCEnabled() { + utils.Fatalf("Account unlock with HTTP access is forbidden!") + } + ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + passwords := MakePasswordList(ctx) + for i, account := range unlocks { + unlockAccount(ks, account, i, passwords) + } +} + +// MakePasswordList reads password lines from the file specified by the global --password flag. +func MakePasswordList(ctx *cli.Context) []string { + path := ctx.Path(PasswordFileFlag.Name) + if path == "" { + return nil + } + text, err := os.ReadFile(path) + if err != nil { + log.Error("Failed to read password ", "file", err) + } + lines := strings.Split(string(text), "\n") + // Sanitise DOS line endings. + for i := range lines { + lines[i] = strings.TrimRight(lines[i], "\r") + } + return lines +} + +func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) { + if err := stack.Start(); err != nil { + log.Critf("Error starting protocol stack: %v", err) + } + go func() { + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(sigc) + + if ctx.IsSet(MinFreeDiskSpaceFlag.Name) { + minFreeDiskSpace := ctx.Int(MinFreeDiskSpaceFlag.Name) + go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024*1024) + } + + shutdown := func() { + log.Info("Got interrupt, shutting down...") + go stack.Close() + for i := 10; i > 0; i-- { + <-sigc + if i > 1 { + log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) + } + } + panic("Panic closing the ast node") + } + + if isConsole { + // In JS console mode, SIGINT is ignored because it's handled by the console. + // However, SIGTERM still shuts down the node. + for { + sig := <-sigc + if sig == syscall.SIGTERM { + shutdown() + return + } + } + } else { + <-sigc + shutdown() + } + }() +} + +func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) { + if path == "" { + return + } + for { + freeSpace, err := getFreeDiskSpace(path) + if err != nil { + log.Warn("Failed to get free disk space", "path", path, "err", err) + break + } + if freeSpace < freeDiskSpaceCritical { + log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", types.StorageSize(freeSpace), "path", path) + sigc <- syscall.SIGTERM + break + } else if freeSpace < 2*freeDiskSpaceCritical { + log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", types.StorageSize(freeSpace), "critical_level", types.StorageSize(freeDiskSpaceCritical), "path", path) + } + time.Sleep(30 * time.Second) + } +} diff --git a/cmd/ast/cmd.go b/cmd/ast/cmd.go new file mode 100644 index 0000000..5e6bd67 --- /dev/null +++ b/cmd/ast/cmd.go @@ -0,0 +1,558 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "github.com/astranetworld/ast/params/networkname" + "github.com/urfave/cli/v2" +) + +var ( + privateKey string + //engine string + miner bool + // todo + listenAddress = cli.NewStringSlice() + bootstraps = cli.NewStringSlice() + cfgFile string + + p2pStaticPeers = cli.NewStringSlice() + p2pBootstrapNode = cli.NewStringSlice() + p2pDenyList = cli.NewStringSlice() +) + +var rootCmd []*cli.Command + +var networkFlags = []cli.Flag{ + &cli.StringSliceFlag{ + Name: "p2p.listen", + Usage: "p2p listen address", + Value: cli.NewStringSlice(), + Destination: listenAddress, + }, + + &cli.StringSliceFlag{ + Name: "p2p.bootstrap", + Usage: "bootstrap node info", + Value: cli.NewStringSlice(), + Destination: bootstraps, + }, + + &cli.StringFlag{ + Name: "p2p.key", + Usage: "private key of p2p node", + Value: "", + Destination: &DefaultConfig.NetworkCfg.LocalPeerKey, + }, +} + +var nodeFlg = []cli.Flag{ + &cli.StringFlag{ + Name: "node.key", + Usage: "node private", + Value: "", + Destination: &DefaultConfig.NodeCfg.NodePrivate, + }, +} + +var rpcFlags = []cli.Flag{ + + &cli.StringFlag{ + Name: "ipcpath", + Usage: "Filename for IPC socket/pipe within the data dir (explicit paths escape it)", + Value: DefaultConfig.NodeCfg.IPCPath, + Destination: &DefaultConfig.NodeCfg.IPCPath, + }, + + &cli.BoolFlag{ + Name: "http", + Usage: "Enable the HTTP json-rpc server", + Value: false, + Destination: &DefaultConfig.NodeCfg.HTTP, + }, + &cli.StringFlag{ + Name: "http.addr", + Usage: "HTTP server listening interface", + Value: DefaultConfig.NodeCfg.HTTPHost, + Destination: &DefaultConfig.NodeCfg.HTTPHost, + }, + &cli.StringFlag{ + Name: "http.port", + Usage: "HTTP server listening port", + Value: "20012", + Destination: &DefaultConfig.NodeCfg.HTTPPort, + }, + &cli.StringFlag{ + Name: "http.api", + Usage: "API's offered over the HTTP-RPC interface", + Value: "", + Destination: &DefaultConfig.NodeCfg.HTTPApi, + }, + + &cli.StringFlag{ + Name: "http.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: "", + Destination: &DefaultConfig.NodeCfg.HTTPCors, + }, + + &cli.BoolFlag{ + Name: "ws", + Usage: "Enable the WS-RPC server", + Value: false, + Destination: &DefaultConfig.NodeCfg.WS, + }, + &cli.StringFlag{ + Name: "ws.addr", + Usage: "WS-RPC server listening interface", + Value: DefaultConfig.NodeCfg.WSHost, + Destination: &DefaultConfig.NodeCfg.WSHost, + }, + &cli.StringFlag{ + Name: "ws.port", + Usage: "WS-RPC server listening port", + Value: "20013", + Destination: &DefaultConfig.NodeCfg.WSPort, + }, + + &cli.StringFlag{ + Name: "ws.api", + Usage: "API's offered over the WS-RPC interface", + Value: "", + Destination: &DefaultConfig.NodeCfg.WSApi, + }, + + &cli.StringFlag{ + Name: "ws.origins", + Usage: "Origins from which to accept websockets requests", + Value: "", + Destination: &DefaultConfig.NodeCfg.WSOrigins, + }, +} + +var consensusFlag = []cli.Flag{ + //&cli.StringFlag{ + // Name: "engine.type", + // Usage: "consensus engine", + // Value: "APosEngine", //APoaEngine,APosEngine + // Destination: &DefaultConfig.ChainCfg.Consensus, + //}, + &cli.BoolFlag{ + Name: "engine.miner", + Usage: "miner", + Value: false, + Destination: &DefaultConfig.NodeCfg.Miner, + }, + &cli.StringFlag{ + Name: "engine.etherbase", + Usage: "consensus etherbase", + Value: "", + Destination: &DefaultConfig.Miner.Etherbase, + }, +} + +var configFlag = []cli.Flag{ + &cli.StringFlag{ + Name: "blockchain", + Usage: "Loading a Configuration File", + Destination: &cfgFile, + }, +} + +var pprofCfg = []cli.Flag{ + &cli.BoolFlag{ + Name: "pprof", + Usage: "Enable the pprof HTTP server", + Value: false, + Destination: &DefaultConfig.PprofCfg.Pprof, + }, + + &cli.BoolFlag{ + Name: "pprof.block", + Usage: "Turn on block profiling", + Value: false, + Destination: &DefaultConfig.PprofCfg.TraceBlock, + }, + &cli.BoolFlag{ + Name: "pprof.mutex", + Usage: "Turn on mutex profiling", + Value: false, + Destination: &DefaultConfig.PprofCfg.TraceMutex, + }, + &cli.IntFlag{ + Name: "pprof.maxcpu", + Usage: "setup number of cpu", + Value: 0, + Destination: &DefaultConfig.PprofCfg.MaxCpu, + }, + &cli.IntFlag{ + Name: "pprof.port", + Usage: "pprof HTTP server listening port", + Value: 0, + Destination: &DefaultConfig.PprofCfg.Port, + }, +} + +var loggerFlag = []cli.Flag{ + &cli.StringFlag{ + Name: "log.name", + Usage: "logger file name and path", + Value: "ast.log", + Destination: &DefaultConfig.LoggerCfg.LogFile, + }, + + &cli.StringFlag{ + Name: "log.level", + Usage: "logger output level (value:[debug,info,warn,error,dpanic,panic,fatal])", + Value: "debug", + Destination: &DefaultConfig.LoggerCfg.Level, + }, + + &cli.IntFlag{ + Name: "log.maxSize", + Usage: "logger file max size M", + Value: 10, + Destination: &DefaultConfig.LoggerCfg.MaxSize, + }, + &cli.IntFlag{ + Name: "log.maxBackups", + Usage: "logger file max backups", + Value: 10, + Destination: &DefaultConfig.LoggerCfg.MaxBackups, + }, + &cli.IntFlag{ + Name: "log.maxAge", + Usage: "logger file max age", + Value: 30, + Destination: &DefaultConfig.LoggerCfg.MaxAge, + }, + &cli.BoolFlag{ + Name: "log.compress", + Usage: "logger file compress", + Value: false, + Destination: &DefaultConfig.LoggerCfg.Compress, + }, +} +var ( + // P2PNoDiscovery specifies whether we are running a local network and have no need for connecting + // to the bootstrap nodes in the cloud + P2PNoDiscovery = &cli.BoolFlag{ + Name: "p2p.no-discovery", + Usage: "Enable only local network p2p and do not connect to cloud bootstrap nodes.", + Destination: &DefaultConfig.P2PCfg.NoDiscovery, + } + // P2PStaticPeers specifies a set of peers to connect to explicitly. + P2PStaticPeers = &cli.StringSliceFlag{ + Name: "p2p.peer", + Usage: "Connect with this peer. This flag may be used multiple times.", + Destination: p2pStaticPeers, + } + // P2PBootstrapNode tells the beacon node which bootstrap node to connect to + P2PBootstrapNode = &cli.StringSliceFlag{ + Name: "p2p.bootstrap-node", + Usage: "The address of bootstrap node. Beacon node will connect for peer discovery via DHT. Multiple nodes can be passed by using the flag multiple times but not comma-separated. You can also pass YAML files containing multiple nodes.", + Destination: p2pBootstrapNode, + } + // P2PRelayNode tells the beacon node which relay node to connect to. + P2PRelayNode = &cli.StringFlag{ + Name: "p2p.relay-node", + Usage: "The address of relay node. The beacon node will connect to the " + + "relay node and advertise their address via the relay node to other peers", + Value: "", + Destination: &DefaultConfig.P2PCfg.RelayNodeAddr, + } + // P2PUDPPort defines the port to be used by discv5. + P2PUDPPort = &cli.IntFlag{ + Name: "p2p.udp-port", + Usage: "The port used by discv5.", + Value: 61015, + Destination: &DefaultConfig.P2PCfg.UDPPort, + } + // P2PTCPPort defines the port to be used by libp2p. + P2PTCPPort = &cli.IntFlag{ + Name: "p2p.tcp-port", + Usage: "The port used by libp2p.", + Value: 61016, + Destination: &DefaultConfig.P2PCfg.TCPPort, + } + // P2PIP defines the local IP to be used by libp2p. + P2PIP = &cli.StringFlag{ + Name: "p2p.local-ip", + Usage: "The local ip address to listen for incoming data.", + Value: "", + Destination: &DefaultConfig.P2PCfg.LocalIP, + } + // P2PHost defines the host IP to be used by libp2p. + P2PHost = &cli.StringFlag{ + Name: "p2p.host-ip", + Usage: "The IP address advertised by libp2p. This may be used to advertise an external IP.", + Value: "", + Destination: &DefaultConfig.P2PCfg.HostAddress, + } + // P2PHostDNS defines the host DNS to be used by libp2p. + P2PHostDNS = &cli.StringFlag{ + Name: "p2p.host-dns", + Usage: "The DNS address advertised by libp2p. This may be used to advertise an external DNS.", + Value: "", + Destination: &DefaultConfig.P2PCfg.HostDNS, + } + // P2PPrivKey defines a flag to specify the location of the private key file for libp2p. + P2PPrivKey = &cli.StringFlag{ + Name: "p2p.priv-key", + Usage: "The file containing the private key to use in communications with other peers.", + Value: "", + Destination: &DefaultConfig.P2PCfg.PrivateKey, + } + P2PStaticID = &cli.BoolFlag{ + Name: "p2p.static-id", + Usage: "Enables the peer id of the node to be fixed by saving the generated network key to the default key path.", + Value: true, + Destination: &DefaultConfig.P2PCfg.StaticPeerID, + } + // P2PMetadata defines a flag to specify the location of the peer metadata file. + P2PMetadata = &cli.StringFlag{ + Name: "p2p.metadata", + Usage: "The file containing the metadata to communicate with other peers.", + Value: "", + Destination: &DefaultConfig.P2PCfg.MetaDataDir, + } + // P2PMaxPeers defines a flag to specify the max number of peers in libp2p. + P2PMaxPeers = &cli.IntFlag{ + Name: "p2p.max-peers", + Usage: "The max number of p2p peers to maintain.", + Value: 5, + Destination: &DefaultConfig.P2PCfg.MaxPeers, + } + // P2PAllowList defines a CIDR subnet to exclusively allow connections. + P2PAllowList = &cli.StringFlag{ + Name: "p2p.allowlist", + Usage: "The CIDR subnet for allowing only certain peer connections. " + + "Using \"public\" would allow only public subnets. Example: " + + "192.168.0.0/16 would permit connections to peers on your local network only. The " + + "default is to accept all connections.", + Destination: &DefaultConfig.P2PCfg.AllowListCIDR, + } + // P2PDenyList defines a list of CIDR subnets to disallow connections from them. + P2PDenyList = &cli.StringSliceFlag{ + Name: "p2p.denylist", + Usage: "The CIDR subnets for denying certainty peer connections. " + + "Using \"private\" would deny all private subnets. Example: " + + "192.168.0.0/16 would deny connections from peers on your local network only. The " + + "default is to accept all connections.", + Destination: p2pDenyList, + } + + // P2PMinSyncPeers specifies the required number of successful peer handshakes in order + // to start syncing with external peers. + P2PMinSyncPeers = &cli.IntFlag{ + Name: "p2p.min-sync-peers", + Usage: "The required number of valid peers to connect with before syncing.", + Value: 1, + Destination: &DefaultConfig.P2PCfg.MinSyncPeers, + } + + // P2PBlockBatchLimit specifies the requested block batch size. + P2PBlockBatchLimit = &cli.IntFlag{ + Name: "p2p.limit.block-batch", + Usage: "The amount of blocks the local peer is bounded to request and respond to in a batch.", + Value: 64, + Destination: &DefaultConfig.P2PCfg.P2PLimit.BlockBatchLimit, + } + // P2PBlockBatchLimitBurstFactor specifies the factor by which block batch size may increase. + P2PBlockBatchLimitBurstFactor = &cli.IntFlag{ + Name: "p2p.limit.block-burst-factor", + Usage: "The factor by which block batch limit may increase on burst.", + Value: 2, + Destination: &DefaultConfig.P2PCfg.P2PLimit.BlockBatchLimitBurstFactor, + } + // P2PBlockBatchLimiterPeriod Period to calculate expected limit for a single peer. + P2PBlockBatchLimiterPeriod = &cli.IntFlag{ + Name: "p2p.limit.block-limiter-period", + Usage: "Period to calculate expected limit for a single peer.", + Value: 5, + Destination: &DefaultConfig.P2PCfg.P2PLimit.BlockBatchLimiterPeriod, + } +) + +var ( + DataDirFlag = &cli.StringFlag{ + Name: "data.dir", + Usage: "data save dir", + Value: "./ast/", + Destination: &DefaultConfig.NodeCfg.DataDir, + } + + MinFreeDiskSpaceFlag = &cli.IntFlag{ + Name: "data.dir.minfreedisk", + Usage: "Minimum free disk space in GB, once reached triggers auto shut down (default = 10GB, 0 = disabled)", + Value: 10, + Destination: &DefaultConfig.NodeCfg.MinFreeDiskSpace, + } + + FromDataDirFlag = &cli.StringFlag{ + Name: "chaindata.from", + Usage: "source data dir", + } + ToDataDirFlag = &cli.StringFlag{ + Name: "chaindata.to", + Usage: "to data dir", + } + + ChainFlag = &cli.StringFlag{ + Name: "chain", + Usage: "Name of the testnet to join (value:[mainnet,testnet,private])", + Value: networkname.MainnetChainName, + Destination: &DefaultConfig.NodeCfg.Chain, + } +) + +var ( + AuthRPCFlag = &cli.BoolFlag{ + Name: "authrpc", + Usage: "Enable the AUTH-RPC server", + Value: false, + Destination: &DefaultConfig.NodeCfg.AuthRPC, + } + // Authenticated RPC HTTP settings + AuthRPCListenFlag = &cli.StringFlag{ + Name: "authrpc.addr", + Usage: "Listening address for authenticated APIs", + Value: "", + Destination: &DefaultConfig.NodeCfg.AuthAddr, + } + AuthRPCPortFlag = &cli.IntFlag{ + Name: "authrpc.port", + Usage: "Listening port for authenticated APIs", + Destination: &DefaultConfig.NodeCfg.AuthPort, + } + JWTSecretFlag = &cli.StringFlag{ + Name: "authrpc.jwtsecret", + Usage: "Path to a JWT secret to use for authenticated RPC endpoints", + Value: "", + Destination: &DefaultConfig.NodeCfg.JWTSecret, + } +) + +var ( + // Account settings + UnlockedAccountFlag = &cli.StringFlag{ + Name: "account.unlock", + Usage: "Comma separated list of accounts to unlock", + Value: "", + } + PasswordFileFlag = &cli.PathFlag{ + Name: "account.password", + Usage: "Password file to use for non-interactive password input", + Destination: &DefaultConfig.NodeCfg.PasswordFile, + } + LightKDFFlag = &cli.BoolFlag{ + Name: "account.lightkdf", + Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", + } + KeyStoreDirFlag = &cli.PathFlag{ + Name: "account.keystore", + Usage: "Directory for the keystore (default = inside the datadir)", + TakesFile: true, + Destination: &DefaultConfig.NodeCfg.KeyStoreDir, + } + InsecureUnlockAllowedFlag = &cli.BoolFlag{ + Name: "account.allow.insecure.unlock", + Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http", + Value: false, + Destination: &DefaultConfig.NodeCfg.InsecureUnlockAllowed, + } + + // MetricsEnabledFlag Metrics flags + MetricsEnabledFlag = &cli.BoolFlag{ + Name: "metrics", + Usage: "Enable metrics collection and reporting", + Value: false, + Destination: &DefaultConfig.MetricsCfg.Enable, + } + + // MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint. + // Since the pprof service enables sensitive/vulnerable behavior, this allows a user + // to enable a public-OK metrics endpoint without having to worry about ALSO exposing + // other profiling behavior or information. + MetricsHTTPFlag = &cli.StringFlag{ + Name: "metrics.addr", + Usage: `Enable stand-alone metrics HTTP server listening interface.`, + //Category: flags.MetricsCategory, + Value: "127.0.0.1", + Destination: &DefaultConfig.MetricsCfg.HTTP, + } + MetricsPortFlag = &cli.IntFlag{ + Name: "metrics.port", + Usage: `Metrics HTTP server listening port. +Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.`, + Value: 6060, + // Category: flags.MetricsCategory, + Destination: &DefaultConfig.MetricsCfg.Port, + } +) + +var ( + authRPCFlag = []cli.Flag{ + AuthRPCFlag, + AuthRPCListenFlag, + AuthRPCPortFlag, + JWTSecretFlag, + } + settingFlag = []cli.Flag{ + DataDirFlag, + ChainFlag, + MinFreeDiskSpaceFlag, + } + accountFlag = []cli.Flag{ + PasswordFileFlag, + KeyStoreDirFlag, + LightKDFFlag, + InsecureUnlockAllowedFlag, + UnlockedAccountFlag, + } + + metricsFlags = []cli.Flag{ + MetricsEnabledFlag, + MetricsHTTPFlag, + MetricsPortFlag, + } + + p2pFlags = []cli.Flag{ + P2PNoDiscovery, + P2PAllowList, + P2PBootstrapNode, + P2PDenyList, + P2PIP, + P2PHost, + P2PMaxPeers, + P2PMetadata, + P2PStaticID, + P2PPrivKey, + P2PHostDNS, + P2PRelayNode, + P2PStaticPeers, + P2PUDPPort, + P2PTCPPort, + P2PMinSyncPeers, + } + + p2pLimitFlags = []cli.Flag{ + P2PBlockBatchLimit, + P2PBlockBatchLimitBurstFactor, + P2PBlockBatchLimiterPeriod, + } +) diff --git a/cmd/ast/config.go b/cmd/ast/config.go new file mode 100644 index 0000000..1048e41 --- /dev/null +++ b/cmd/ast/config.go @@ -0,0 +1,78 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "github.com/astranetworld/ast/params" + "math/big" + "time" + + "github.com/astranetworld/ast/conf" +) + +var DefaultConfig = conf.Config{ + NodeCfg: conf.NodeConfig{ + NodePrivate: "", + HTTP: true, + HTTPHost: "127.0.0.1", + HTTPPort: "8545", + IPCPath: "ast.ipc", + Miner: false, + }, + NetworkCfg: conf.NetWorkConfig{ + Bootstrapped: true, + }, + LoggerCfg: conf.LoggerConfig{ + LogFile: "./logger.log", + Level: "debug", + MaxSize: 10, + MaxBackups: 10, + MaxAge: 30, + Compress: true, + }, + PprofCfg: conf.PprofConfig{ + MaxCpu: 0, + Port: 6060, + TraceMutex: true, + TraceBlock: true, + Pprof: false, + }, + DatabaseCfg: conf.DatabaseConfig{ + DBType: "lmdb", + DBPath: "chaindata", + DBName: "ast", + SubDB: []string{"chain"}, + Debug: false, + IsMem: false, + MaxDB: 100, + MaxReaders: 1000, + }, + MetricsCfg: conf.MetricsConfig{ + Port: 6060, + HTTP: "127.0.0.1", + }, + + P2PCfg: &conf.P2PConfig{P2PLimit: &conf.P2PLimit{}}, + + //GenesisCfg: ReadGenesis("allocs/mainnet.json"), + GPO: conf.FullNodeGPO, + Miner: conf.MinerConfig{ + GasCeil: 30000000, + GasPrice: big.NewInt(params.GWei), + Recommit: 4 * time.Second, + }, +} diff --git a/cmd/ast/diskusage.go b/cmd/ast/diskusage.go new file mode 100644 index 0000000..b22b426 --- /dev/null +++ b/cmd/ast/diskusage.go @@ -0,0 +1,44 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build !windows && !openbsd +// +build !windows,!openbsd + +package main + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + var bavail = stat.Bavail + // nolint:staticcheck + if stat.Bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.Bsize), nil +} diff --git a/cmd/ast/diskusage_openbsd.go b/cmd/ast/diskusage_openbsd.go new file mode 100644 index 0000000..707c028 --- /dev/null +++ b/cmd/ast/diskusage_openbsd.go @@ -0,0 +1,44 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build openbsd +// +build openbsd + +package main + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + var bavail = stat.F_bavail + // Not sure if the following check is necessary for OpenBSD + if stat.F_bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.F_bsize), nil +} diff --git a/cmd/ast/diskusage_windows.go b/cmd/ast/diskusage_windows.go new file mode 100644 index 0000000..a5576ec --- /dev/null +++ b/cmd/ast/diskusage_windows.go @@ -0,0 +1,38 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +func getFreeDiskSpace(path string) (uint64, error) { + + cwd, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, fmt.Errorf("failed to call UTF16PtrFromString: %v", err) + } + + var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64 + if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil { + return 0, fmt.Errorf("failed to call GetDiskFreeSpaceEx: %v", err) + } + + return freeBytesAvailableToCaller, nil +} diff --git a/cmd/ast/exportcmd.go b/cmd/ast/exportcmd.go new file mode 100644 index 0000000..ef0a7c5 --- /dev/null +++ b/cmd/ast/exportcmd.go @@ -0,0 +1,228 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "context" + "errors" + "fmt" + "github.com/astranetworld/ast/common/account" + common "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/node" + "github.com/astranetworld/ast/log" + "github.com/astranetworld/ast/modules" + "github.com/astranetworld/ast/params" + "github.com/astranetworld/ast/turbo/backup" + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/urfave/cli/v2" + "math/big" + "os" + "time" +) + +var ( + exportCommand = &cli.Command{ + Name: "export", + Usage: "Export astranet data", + ArgsUsage: "", + Description: ``, + Subcommands: []*cli.Command{ + { + Name: "txs", + Usage: "Export All astranet Transactions", + ArgsUsage: "", + Action: exportTransactions, + Flags: []cli.Flag{ + DataDirFlag, + }, + Description: ``, + }, + { + Name: "balance", + Usage: "Export All astranet account balance", + ArgsUsage: "", + Action: exportBalance, + Flags: []cli.Flag{ + DataDirFlag, + }, + Description: ``, + }, + { + Name: "dbState", + Usage: "Export All MDBX Buckets disk space", + ArgsUsage: "", + Action: exportDBState, + Flags: []cli.Flag{ + DataDirFlag, + }, + Description: ``, + }, + { + Name: "dbCopy", + Usage: "copy data from '--chaindata' to '--chaindata.to'", + ArgsUsage: "", + Action: dbCopy, + Flags: []cli.Flag{ + FromDataDirFlag, + ToDataDirFlag, + }, + Description: ``, + }, + }, + } +) + +func exportTransactions(ctx *cli.Context) error { + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + + blockChain := stack.BlockChain() + defer stack.Close() + + currentBlock := blockChain.CurrentBlock() + + for i := uint64(0); i < currentBlock.Number64().Uint64(); i++ { + + block, err := blockChain.GetBlockByNumber(uint256.NewInt(i + 1)) + if err != nil { + panic("cannot get block") + } + for _, transaction := range block.Transactions() { + if transaction.To() == nil { + continue + } + fmt.Printf("%d,%s,%s,%d,%s,%.2f\n", + block.Number64().Uint64(), + time.Unix(int64(block.Time()), 0).Format(time.RFC3339), + transaction.From().Hex(), + transaction.Nonce(), + transaction.To().Hex(), + new(big.Float).Quo(new(big.Float).SetInt(transaction.Value().ToBig()), new(big.Float).SetInt(big.NewInt(params.AMT))), + ) + } + } + + return nil +} + +func exportBalance(ctx *cli.Context) error { + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + db := stack.Database() + defer stack.Close() + + roTX, err := db.BeginRo(ctx.Context) + if err != nil { + return err + } + defer roTX.Rollback() + //kv.ReadAhead(ctx.Context, roTX.(kv.RoDB), atomic.NewBool(false), name, nil, 1<<32-1) // MaxUint32 + // + srcC, err := roTX.Cursor("Account") + if err != nil { + return err + } + + for k, v, err := srcC.First(); k != nil; k, v, err = srcC.Next() { + if err != nil { + return err + } + var acc account.StateAccount + if err = acc.DecodeForStorage(v); err != nil { + return err + } + + fmt.Printf("%x, %.2f\n", + k, + new(big.Float).Quo(new(big.Float).SetInt(acc.Balance.ToBig()), new(big.Float).SetInt(big.NewInt(params.AMT))), + ) + } + + return nil +} + +func exportDBState(ctx *cli.Context) error { + + stack, err := node.NewNode(ctx, &DefaultConfig) + if err != nil { + return err + } + db := stack.Database() + defer stack.Close() + + var tsize uint64 + + roTX, err := db.BeginRo(ctx.Context) + if err != nil { + return err + } + defer roTX.Rollback() + + migrator, ok := roTX.(kv.BucketMigrator) + if !ok { + return fmt.Errorf("cannot open db as BucketMigrator") + } + Buckets, err := migrator.ListBuckets() + for _, Bucket := range Buckets { + size, _ := roTX.BucketSize(Bucket) + tsize += size + Cursor, _ := roTX.Cursor(Bucket) + count, _ := Cursor.Count() + Cursor.Close() + if count != 0 { + fmt.Printf("%30v count %10d size: %s \r\n", Bucket, count, common.StorageSize(size)) + } + } + fmt.Printf("total %s \n", common.StorageSize(tsize)) + return nil +} + +func dbCopy(ctx *cli.Context) error { + + modules.AstInit() + kv.ChaindataTablesCfg = modules.AstTableCfg + + fromChaindata := ctx.String(FromDataDirFlag.Name) + toChaindata := ctx.String(ToDataDirFlag.Name) + + if f, err := os.Stat(fromChaindata); err != nil || !f.IsDir() { + log.Errorf("fromChaindata do not exists or is not a dir, err: %s", err) + return err + } + if f, err := os.Stat(toChaindata); err != nil || !f.IsDir() { + log.Errorf("toChaindata do not exists or is not a dir, err: %s", err) + return err + } + + from, to := backup.OpenPair(fromChaindata, toChaindata, kv.ChainDB, 0) + err := backup.Kv2kv(ctx.Context, from, to, nil, backup.ReadAheadThreads) + if err != nil && !errors.Is(err, context.Canceled) { + if !errors.Is(err, context.Canceled) { + log.Error(err.Error()) + } + return nil + } + + return nil +} diff --git a/cmd/ast/initcmd.go b/cmd/ast/initcmd.go new file mode 100644 index 0000000..bbe7dd5 --- /dev/null +++ b/cmd/ast/initcmd.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/astranetworld/ast/cmd/utils" + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/conf" + "github.com/astranetworld/ast/internal/node" + "github.com/astranetworld/ast/log" + "github.com/astranetworld/ast/modules/rawdb" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/urfave/cli/v2" + "os" +) + +var ( + initCommand = &cli.Command{ + Name: "init", + Usage: "Bootstrap and initialize a new genesis block", + ArgsUsage: "", + Action: initGenesis, + Flags: []cli.Flag{ + DataDirFlag, + }, + Description: ` +The init command initializes a new genesis block and definition for the network. +This is a destructive action and changes the network in which you will be +participating. + +It expects the genesis file as argument.`, + } +) + +// initGenesis will initialise the given JSON format genesis file and writes it as +// the zero'd block (i.e. genesis) or will fail hard if it can't succeed. +func initGenesis(cliCtx *cli.Context) error { + var ( + err error + genesisBlock *block.Block + ) + + // Make sure we have a valid genesis JSON + genesisPath := cliCtx.Args().First() + if len(genesisPath) == 0 { + utils.Fatalf("Must supply path to genesis JSON file") + } + + file, err := os.Open(genesisPath) + if err != nil { + utils.Fatalf("Failed to read genesis file: %v", err) + } + defer file.Close() + + genesis := new(conf.Genesis) + if err := json.NewDecoder(file).Decode(genesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + + chaindb, err := node.OpenDatabase(&DefaultConfig, nil, kv.ChainDB.String()) + if err != nil { + utils.Fatalf("Failed to open database: %v", err) + } + defer chaindb.Close() + + if err := chaindb.Update(context.TODO(), func(tx kv.RwTx) error { + storedHash, err := rawdb.ReadCanonicalHash(tx, 0) + if err != nil { + return err + } + + if storedHash != (types.Hash{}) { + return fmt.Errorf("genesis state are already exists") + + } + genesisBlock, err = node.WriteGenesisBlock(tx, genesis) + if nil != err { + return err + } + if err := node.WriteChainConfig(tx, genesisBlock.Hash(), genesis); err != nil { + return err + } + return nil + }); err != nil { + utils.Fatalf("Failed to wrote genesis state to database: %w", err) + } + log.Info("Successfully wrote genesis state", "hash", genesisBlock.Hash()) + return nil +} diff --git a/cmd/ast/logger.go b/cmd/ast/logger.go new file mode 100644 index 0000000..4628253 --- /dev/null +++ b/cmd/ast/logger.go @@ -0,0 +1,87 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +// +//var ( +// once sync.Once +//) +// +//func Init(nodeConfig *conf.NodeConfig, config *conf.LoggerConfig) (*zap.Logger, error) { +// +// var level zapcore.Level +// if err := level.Set(config.Level); err != nil { +// return nil, err +// } +// +// infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { +// return lvl >= level && lvl < zapcore.ErrorLevel +// }) +// +// errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { +// return lvl >= zapcore.ErrorLevel +// }) +// +// var Logger *zap.Logger +// +// once.Do(func() { +// encode := getEncoder() +// core := zapcore.NewTee( +// zapcore.NewCore(encode, getLogWriter(nodeConfig, config), infoLevel), +// zapcore.NewCore(encode, zapcore.AddSync(os.Stdout), level), +// zapcore.NewCore(encode, getErrorWriter(nodeConfig, config), errorLevel), +// ) +// +// Logger = zap.New(core, zap.AddCaller()) +// }) +// +// return Logger, nil +//} +// +//func getEncoder() zapcore.Encoder { +// encoderConfig := zap.NewProductionEncoderConfig() +// encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder +// encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder +// encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder +// encoderConfig.EncodeName = zapcore.FullNameEncoder +// return zapcore.NewConsoleEncoder(encoderConfig) +//} +// +//func getLumberjack(path string, maxSize, maxBackups, maxAge int, compress bool) *lumberjackV2.Logger { +// return &lumberjackV2.Logger{ +// Filename: path, +// MaxSize: maxSize, +// MaxBackups: maxBackups, +// MaxAge: maxAge, +// Compress: compress, +// } +//} +// +//func getLogWriter(nodeConfig *conf.NodeConfig, config *conf.LoggerConfig) zapcore.WriteSyncer { +// return zapcore.AddSync(getLumberjack(fmt.Sprintf("%s/log/%s", nodeConfig.DataDir, config.LogFile), config.MaxSize, config.MaxBackups, config.MaxAge, config.Compress)) +//} +// +//func getErrorWriter(nodeConfig *conf.NodeConfig, config *conf.LoggerConfig) zapcore.WriteSyncer { +// var name string +// i := strings.LastIndex(config.LogFile, ".") +// if i <= 0 { +// name = fmt.Sprintf("%s_error.log", config.LogFile) +// } else { +// name = fmt.Sprintf("%s_error.log", config.LogFile[:i]) +// } +// return zapcore.AddSync(getLumberjack(fmt.Sprintf("%s/log/%s", nodeConfig.DataDir, name), config.MaxSize, config.MaxBackups, config.MaxAge, config.Compress)) +//} diff --git a/cmd/ast/main.go b/cmd/ast/main.go new file mode 100644 index 0000000..ef74db8 --- /dev/null +++ b/cmd/ast/main.go @@ -0,0 +1,62 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "fmt" + "github.com/urfave/cli/v2" + "os" + + "github.com/astranetworld/ast/params" + // Force-load the tracer engines to trigger registration + _ "github.com/astranetworld/ast/internal/tracers/js" + _ "github.com/astranetworld/ast/internal/tracers/native" +) + +func main() { + flags := append(networkFlags, consensusFlag...) + flags = append(flags, loggerFlag...) + flags = append(flags, pprofCfg...) + flags = append(flags, nodeFlg...) + flags = append(flags, rpcFlags...) + flags = append(flags, authRPCFlag...) + flags = append(flags, configFlag...) + flags = append(flags, settingFlag...) + flags = append(flags, accountFlag...) + flags = append(flags, metricsFlags...) + flags = append(flags, p2pFlags...) + flags = append(flags, p2pLimitFlags...) + + rootCmd = append(rootCmd, walletCommand, accountCommand, exportCommand, initCommand) + commands := rootCmd + + app := &cli.App{ + Name: "ast", + Usage: "astranet system", + Flags: flags, + Commands: commands, + //Version: version.FormatVersion(), + Version: params.VersionWithCommit(params.GitCommit, ""), + UseShortOptionHandling: true, + Action: appRun, + } + + err := app.Run(os.Args) + if err != nil { + fmt.Printf("failed ast system setup %v", err) + } +} diff --git a/cmd/evmsdk/blst.go b/cmd/evmsdk/blst.go new file mode 100644 index 0000000..db98c4f --- /dev/null +++ b/cmd/evmsdk/blst.go @@ -0,0 +1,57 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package evmsdk + +import ( + "encoding/hex" + + "github.com/astranetworld/ast/common/crypto/bls" +) + +func BlsSign(privKey, msg string) (interface{}, error) { + msgBytes, err := hex.DecodeString(msg) + if err != nil { + return nil, err + } + privKeyBytes, err := hex.DecodeString(privKey) + if err != nil { + return nil, err + } + arr := [32]byte{} + copy(arr[:], privKeyBytes[:]) + sk, err := bls.SecretKeyFromRandom32Byte(arr) + if err != nil { + return nil, err + } + resBytes := sk.Sign(msgBytes).Marshal() + return hex.EncodeToString(resBytes), nil +} + +func BlsPublicKey(privKey string) (interface{}, error) { + privKeyBytes, err := hex.DecodeString(privKey) + if err != nil { + return nil, err + } + arr := [32]byte{} + copy(arr[:], privKeyBytes[:]) + sk, err := bls.SecretKeyFromRandom32Byte(arr) + if err != nil { + return nil, err + } + resBytes := sk.PublicKey().Marshal() + return hex.EncodeToString(resBytes), nil +} diff --git a/cmd/evmsdk/common.go b/cmd/evmsdk/common.go new file mode 100644 index 0000000..2d76bd7 --- /dev/null +++ b/cmd/evmsdk/common.go @@ -0,0 +1,958 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package evmsdk + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/crypto/ecies" + "github.com/astranetworld/ast/common/hexutil" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + commTyp "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "github.com/astranetworld/ast/modules/state" + "github.com/go-kit/kit/transport/http/jsonrpc" + "github.com/gorilla/websocket" + + "github.com/astranetworld/ast/common/crypto/bls" +) + +const ( + VERSION = "" + EngineStateRunning = "running" + EngineStateStopped = "stopped" + + RequestPath = "" + ConfigPath = "evm" + LogFile = "vertification_debug_log.txt" +) + +var ( + IS_DEBUG = true +) + +var EE *EvmEngine = &EvmEngine{} + +func Emit(jsonText string) string { + defer func() { + if err := recover(); err != nil { + simpleLog("engine panic,err=%+v", err) + if err := EE.Stop(); err != nil { + simpleLog("engine stop error,err=%+v", err) + } + } + }() + er := new(EmitResponse) + eq := new(EmitRequest) + if err := json.Unmarshal([]byte(jsonText), eq); err != nil { + er.Code = 1 + er.Message = err.Error() + return emitJSON(er) + } + + if eq.Val != nil { + simpleLog("emit:", eq.Typ, eq.Val) + } else { + simpleLog("emit:", eq.Typ) + } + + switch eq.Typ { + case "start": + if err := EE.Start(); err != nil { + er.Code, er.Message = 1, err.Error() + if innerErr, ok := err.(*InnerError); ok { + er.Code = innerErr.Code + er.Message = innerErr.Msg + } + return emitJSON(er) + } + case "stop": + if err := EE.Stop(); err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + case "setting": + if err := EE.Setting(eq); err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + case "list": + data, err := EE.List() + if err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + er.Data = data + case "blssign": + data, err := EE.BlsSign(eq.Val) + if err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + er.Data = data + case "blspubk": + data, err := EE.BlsPublicKey(eq.Val) + if err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + er.Data = data + case "state": + data := EE.State() + er.Data = data + case "decrypt": + data, err := EE.Decrypt(eq) + if err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + er.Data = data + case "encrypt": + data, err := EE.Encrypt(eq) + if err != nil { + er.Code, er.Message = 1, err.Error() + return emitJSON(er) + } + er.Data = data + case "test": + er.Message = Test() + } + + return emitJSON(er) +} + +func emitJSON(j interface{}) string { + a, _ := json.Marshal(j) + return string(a) +} + +func (e *EvmEngine) initLogger(lvl string) { + IS_DEBUG = lvl == "debug" + if len(lvl) != 0 { + ensureLogFileExisted() + log.InitMobileLogger(filepath.Join(EE.AppBasePath, LogFile), IS_DEBUG) + } +} + +func ensureLogFileExisted() { + logFilePath := path.Join(EE.AppBasePath, LogFile) + f, err := os.Open(logFilePath) + if err != nil { + f, err = os.Create(logFilePath) + if err != nil { + fmt.Printf("create log file error,err=%+v\n", err) + return + } + } + if err = f.Close(); err != nil { + fmt.Printf("log file close error,err=%+v", err) + } +} + +func simpleLog(txt string, params ...interface{}) { + if IS_DEBUG { + ifces := make([]interface{}, 0, len(params)+1) + ifces = append(ifces, txt) + ifces = append(ifces, params...) + fmt.Println(ifces...) + log.Debug(txt, params...) + } +} + +func simpleLogf(txt string, params ...interface{}) { + if IS_DEBUG { + fmt.Printf(txt+"\n", params...) + log.Debugf(txt, params...) + } +} + +type Setting struct { + Height int + AppBasePath string + Account string + + PrivKey string +} + +type EmitRequest struct { + Typ string `json:"type"` + Val interface{} `json:"val"` +} +type EmitResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type EvmEngine struct { + mu sync.Mutex `json:"-"` + ctx context.Context `json:"-"` + cancelFunc context.CancelFunc `json:"-"` + errChan chan []error `json:"-"` + + Account string `json:"account"` + AppBasePath string `json:"app_base_path"` + EngineState string `json:"state"` + BlockChan chan string `json:"-"` + PrivKey string `json:"priv_key"` + ServerUri string `json:"server_uri"` + LogLevel string `json:"log_level"` +} + +type AccountReward struct { + Account string + Number *uint256.Int + Value *uint256.Int +} +type AccountRewards []*AccountReward + +func (r AccountRewards) Len() int { + return len(r) +} + +func (r AccountRewards) Less(i, j int) bool { + return strings.Compare(r[i].Account, r[j].Account) > 0 +} + +func (r AccountRewards) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +type InnerError struct { + Code int + Msg string +} + +func (i InnerError) Error() string { return fmt.Sprintf("code:%+v,msg:%+v", i.Code, i.Msg) } + +type AggSign struct { + Number uint64 `json:"number"` + StateRoot commTyp.Hash `json:"stateRoot"` + Sign commTyp.Signature `json:"sign"` + PublicKey commTyp.PublicKey `json:"publicKey"` + Address commTyp.Address `json:"address"` +} + +func (e *EvmEngine) Start() error { + e.mu.Lock() + defer e.mu.Unlock() + e.ctx, e.cancelFunc = context.WithCancel(context.Background()) + if e.EngineState == EngineStateRunning { + return fmt.Errorf("evme is running") + } + e.EngineState = EngineStateRunning + + if err := e.verificationTaskBg(); err != nil { + e.EngineState = EngineStateStopped + simpleLogf("launch verification task failed,err=%+v", err.Error()) + return err + } + return nil +} + +func (e *EvmEngine) Stop() error { + e.mu.Lock() + e.EngineState = EngineStateStopped + if e.cancelFunc != nil { + e.cancelFunc() + } + if e.ctx != nil { + e.ctx.Done() + } + e.mu.Unlock() + return nil +} + +func (e *EvmEngine) State() string { + e.mu.Lock() + defer e.mu.Unlock() + if e.EngineState == EngineStateRunning { + return "started" + } else { + return "stopped" + } +} + +/* + { + "type":"setting", + "val":{ + "app_base_path":"/sd/0/evm/", + "account":"abcdef1234" + } + } +*/ +func (e *EvmEngine) Setting(req *EmitRequest) error { + m, ok := req.Val.(map[string]interface{}) + if !ok { + return fmt.Errorf("type assert error,any==>smap") + } + + appBasePath, ok := m["app_base_path"] + if ok { + if appBasePathStr, ok := appBasePath.(string); ok { + e.mu.Lock() + e.AppBasePath = appBasePathStr + e.mu.Unlock() + } + } + if privKeyIfce, ok := m["priv_key"]; ok { + if privKey, ok := privKeyIfce.(string); ok { + e.PrivKey = privKey + } + } + if serverUriIfce, ok := m["server_uri"]; ok { + if serverUri, ok := serverUriIfce.(string); ok { + e.ServerUri = serverUri + } + } + if accIfce, ok := m["account"]; ok { + if account, ok := accIfce.(string); ok { + e.Account = account + } + } + + if logLvlIfce, ok := m["log_level"]; ok { + if loglvlStr, ok := logLvlIfce.(string); ok { + e.initLogger(loglvlStr) + } + } + + return nil +} +func (e *EvmEngine) List() (interface{}, error) { + type l struct { + A string `json:"a"` + B bool `json:"b"` + C int `json:"c"` + } + + ll := []*l{ + { + A: "a", + B: true, + C: 123, + }, { + A: "aa", + B: false, + C: 456, + }, + } + return ll, nil +} +func (e *EvmEngine) SaveFile() error { + return nil +} +func (e *EvmEngine) ReloadFile() error { + return nil +} +func (e *EvmEngine) BlsSign(req interface{}) (interface{}, error) { + var privKey, msg string + blsSignMap, ok := req.(map[string]interface{}) + if !ok { + return nil, errors.New("empty input value") + } + if privKeyIfce, ok := blsSignMap["priv_key"]; ok { + if privKey, ok = privKeyIfce.(string); !ok { + return nil, errors.New("priv_key type : array byte") + } + } else { + privKey = e.PrivKey + } + if msgIfce, ok := blsSignMap["msg"]; ok { + if msg, ok = msgIfce.(string); !ok { + return nil, errors.New("msg type : array byte") + } + } + return BlsSign(privKey, msg) +} +func (e *EvmEngine) BlsPublicKey(req interface{}) (interface{}, error) { + var privKey string + blsPublicKeyMap, ok := req.(map[string]interface{}) + if !ok { + return nil, errors.New("empty input value") + } + if privKeyIfce, ok := blsPublicKeyMap["priv_key"]; ok { + if privKey, ok = privKeyIfce.(string); !ok { + return nil, errors.New("priv_key type: array byte") + } + } else { + privKey = e.PrivKey + } + return BlsPublicKey(privKey) +} + +func (e *EvmEngine) verificationTaskBg() error { + simpleLog("gen pubk") + pubk, err := BlsPublicKey(e.PrivKey) + if err != nil { + simpleLog("generate public key error,", err) + return err + } + simpleLog("init websocket") + wssvr, err := NewWebSocketService(e.ServerUri, e.Account) + if err != nil { + return err + } + simpleLog("init websocket chats") + ochan, ichan, err := wssvr.Chans(pubk.(string)) + if err != nil { + return err + } + go func() { + defer func() { + if err := recover(); err != nil { + buf := make([]byte, 4096) + runtime.Stack(buf, true) + simpleLog("vertification task down", "err", err) + simpleLog(string(buf)) + // simpleLogf("vertification task down,err=%+v,stk:%s:%d", err, f, l) + } + e.mu.Lock() + e.EngineState = EngineStateStopped + e.mu.Unlock() + }() + + for { + select { + case <-e.ctx.Done(): + simpleLog("task has been cancelled") + return + case msg, ok := <-ochan: + if !ok { + simpleLog("task closed") + return + } + entire, err := e.unwrapJSONRPC(msg) + if err != nil { + simpleLog("unwrap jsonrpc message error,err=", err) + continue + } + resp, err := e.vertify(entire) + if err != nil { + simpleLog("ee verification failed", err) + continue + } + ichan <- resp + } + } + }() + return nil +} + +func (e *EvmEngine) Decrypt(req *EmitRequest) (interface{}, error) { + + var ( + params map[string]interface{} + privateKeyInterface interface{} + privateKeyString string + privateKey *ecdsa.PrivateKey + messageInterface interface{} + message string + messageBytes []byte + ok bool + err error + ) + + if params, ok = req.Val.(map[string]interface{}); !ok { + return nil, fmt.Errorf("empty input value") + } + + if privateKeyInterface, ok = params["priv_key"]; !ok { + return nil, fmt.Errorf("privateKey is empty") + } + + if privateKeyString, ok = privateKeyInterface.(string); !ok { + return nil, fmt.Errorf("privateKey type is not string") + } + + if privateKey, err = crypto.HexToECDSA(privateKeyString); err != nil { + return nil, fmt.Errorf("cannot decode private key") + } + + if messageInterface, ok = params["msg"]; !ok { + return nil, fmt.Errorf("msg is empty") + } + if message, ok = messageInterface.(string); !ok { + return nil, fmt.Errorf("msg type is not string") + } + if messageBytes, err = hex.DecodeString(message); err != nil { + return nil, fmt.Errorf("msg cannote decode to bytes") + } + + priKey := ecies.ImportECDSA(privateKey) + + ms, err := priKey.Decrypt(messageBytes, nil, nil) + return hex.EncodeToString(ms), err +} + +func (e *EvmEngine) Encrypt(req *EmitRequest) (interface{}, error) { + + var ( + params map[string]interface{} + publicKeyInterface interface{} + publicKeyString string + publicKeyBytes []byte + publicKey *ecdsa.PublicKey + messageInterface interface{} + message string + messageBytes []byte + ok bool + err error + ) + + if params, ok = req.Val.(map[string]interface{}); !ok { + return nil, fmt.Errorf("empty input value") + } + + if publicKeyInterface, ok = params["public_key"]; !ok { + return nil, fmt.Errorf("public_key is empty") + } + + if publicKeyString, ok = publicKeyInterface.(string); !ok { + return nil, fmt.Errorf("public_key type is not string") + } + + if publicKeyBytes, err = hex.DecodeString(publicKeyString); err != nil { + return nil, fmt.Errorf("public_key cannote decode to bytes") + } + + if publicKey, err = crypto.DecompressPubkey(publicKeyBytes); err != nil { + return nil, fmt.Errorf("cannot decode public key") + } + + if messageInterface, ok = params["msg"]; !ok { + return nil, fmt.Errorf("msg is empty") + } + if message, ok = messageInterface.(string); !ok { + return nil, fmt.Errorf("msg type is not string") + } + + if messageBytes, err = hex.DecodeString(message); err != nil { + return nil, fmt.Errorf("msg cannote decode to bytes") + } + + pubKey := ecies.ImportECDSAPublic(publicKey) + ct, err := ecies.Encrypt(rand.Reader, pubKey, messageBytes, nil, nil) + + return hex.EncodeToString(ct), err +} + +//type innerEntireCode state.EntireCode +// +//func (h *innerEntireCode) UnmarshalJSON(in []byte) error { +// m := map[string]json.RawMessage{} +// if err := json.Unmarshal(in, &m); err != nil { +// return err +// } +// +// if err := json.Unmarshal(m["coinBase"], &h.CoinBase); err != nil { +// return err +// } +// if err := json.Unmarshal(m["codes"], &h.Codes); err != nil { +// return err +// } +// if rewardsBytes, ok := m["rewards"]; ok { +// m := []json.RawMessage{} +// if err := json.Unmarshal(rewardsBytes, &m); err == nil && len(m) != 0 { +// h.Rewards = make([]block.Reward, len(m)) +// for i, _ := range h.Rewards { +// rewardBean := block.Reward{ +// Amount: commTyp.NewInt64(0), +// } +// json.Unmarshal(m[i], &rewardBean) +// h.Rewards[i] = rewardBean +// } +// } +// } +// // if err := json.Unmarshal(m["rewards"], &h.Rewards); err != nil { +// // return err +// // } +// +// h.Entire.Header = &block.Header{ +// Difficulty: commTyp.NewInt64(0), +// Number: commTyp.NewInt64(0), +// BaseFee: commTyp.NewInt64(0), +// } +// if err := json.Unmarshal(m["entire"], &h.Entire); err != nil { +// return err +// } +// +// return nil +//} + +func (e *EvmEngine) vertify(in []byte) ([]byte, error) { + var bean state.EntireCode + if err := json.Unmarshal(in, &bean); err != nil { + simpleLog("unmarshal vertify input error,err=", err) + return nil, err + } + + simpleLog("start verify ", "blockNr", bean.Entire.Header.Number.Uint64()) + + if bean.Entire.Header == nil { + return nil, errors.New("nil pointer found") + } + // before state verify + var hash commTyp.Hash + hasher := sha3.NewLegacyKeccak256() + state.EncodeBeforeState(hasher, bean.Entire.Snap.Items, bean.Codes) + hasher.(crypto.KeccakState).Read(hash[:]) + if bean.Entire.Header.MixDigest != hash { + simpleLog("misMatch state hash", "want", bean.Entire.Header.MixDigest, "get", hash, "block", bean.Entire.Header.Number.Uint64()) + return nil, errors.New("state verify failed") + } + + entirecode := state.EntireCode(bean) + stateRoot := verify(e.ctx, &entirecode) + + res := AggSign{} + //stateroot + copy(res.StateRoot[:], stateRoot[:]) + if pubkIfce, err := BlsPublicKey(e.PrivKey); err == nil { + if pubkStr, ok := pubkIfce.(string); ok { + //publickey + copy(res.PublicKey[:], []byte(pubkStr)) + } + } + + simpleLog("calculated stateRoot:", "stateRoot", hexutil.Encode(res.StateRoot[:])) + + //privkey + res.Number = bean.Entire.Header.Number.Uint64() + privKeyBytes, err := hex.DecodeString(e.PrivKey) + if err != nil { + return nil, err + } + arr := [32]byte{} + copy(arr[:], privKeyBytes[:]) + sk, err := bls.SecretKeyFromRandom32Byte(arr) + if err != nil { + return nil, err + } + + //sign + copy(res.Sign[:], sk.Sign(res.StateRoot[:]).Marshal()) + + simpleLog("sign stateRoot:", "Sign", hexutil.Encode(res.Sign[:])) + + //address + res.Address = commTyp.HexToAddress(e.Account) + + resBytes, err := json.Marshal(res) + if err != nil { + return nil, err + } + + return resBytes, nil +} + +func (e *EvmEngine) unwrapJSONRPC(in []byte) ([]byte, error) { + //"{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32000,\"message\":\"unauthed address: 0xeB156a42dcaFcf155B07f3638892440C7dE5d564\"}}\n" + //ws consumer received msg:%!(EXTRA string=ws consumer received msg:, string={"jsonrpc":"2.0","id":1,"result":"0x96410b68a9f8875bb20fde06823eb861"} + req := new(jsonrpc.Request) + if err := json.Unmarshal(in, req); err != nil { + return nil, err + } + if len(req.Params) == 0 { + return []byte{}, errors.New("empty request params") + } + + //type innerProtocolEntire struct { + // Entire json.RawMessage `json:"Entire"` + //} + type innerProtocol struct { + Subscription string `json:"subscription"` + Result json.RawMessage `json:"result"` + } + + innerReq := new(innerProtocol) + if err := json.Unmarshal(req.Params, innerReq); err != nil { + return nil, err + } + + return innerReq.Result, nil +} + +//======test methods + +func Test() string { + var sw strings.Builder + + sw.WriteString("version:" + VERSION + "\r\n") + + engJsonBytes, err := json.Marshal(EE) + if err == nil { + sw.WriteString(string(engJsonBytes) + "\r\n") + } + + sw.WriteString("gettime:" + strconv.Itoa(int(GetTime())) + "\r\n") + + sw.WriteString("getapppath:" + GetAppPath() + "\r\n") + + sw.WriteString("getjson:" + GetJson() + "\r\n") + + sw.WriteString("readfile_before:" + ReadTouchedFile() + "\r\n") + sw.WriteString("touchfile:" + TouchFile() + "\r\n") + sw.WriteString("readfile_after:" + ReadTouchedFile() + "\r\n") + + ns := GetNetInfos() + sw.WriteString("getnetinfos:resplen:" + strconv.Itoa(len(ns)) + "\r\n") + + sw.WriteString("backgroundthread:" + BackgroundLoop() + "\r\n") + + sw.WriteString("bls tests " + BlsTest() + "\r\n") + + //block here + sw.WriteString("wsocket:" + GetWebSocketConnect() + "\r\n") + + return sw.String() +} + +func GetTime() int64 { + return time.Now().Unix() +} + +func GetAppPath() string { + path, err := os.Getwd() + if err != nil { + return err.Error() + } + + return fmt.Sprintf("exec path:%+v;;setting_path:%+v", path, EE.AppBasePath) +} + +func GetJson() string { + return `{ + "a":"b", + "c":"d" + }` +} + +func TouchFile() string { + path := path.Join(EE.AppBasePath, "evm_touched_file.txt") + file, err := os.Create(path) + if err != nil { + return err.Error() + } + defer file.Close() + t := time.Now().Unix() + n, err := file.WriteString(strconv.Itoa(int(t)) + "|NAME=wux PWD=$WORK/src GOOS=android GOARCH=386 CC=$ANDROID_HOME/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang CXX=$ANDROID_HOME/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang++ CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go mod tidy") + if err != nil { + return err.Error() + } + if n == 0 { + return "n==0" + } + return file.Name() +} + +func ReadTouchedFile() string { + path := path.Join(EE.AppBasePath, "evm_touched_file.txt") + file, err := os.Open(path) + if err != nil { + return fmt.Sprintln("open touched file error,err=", err.Error()) + } + defer file.Close() + allBytes, err := io.ReadAll(file) + if err != nil { + return fmt.Sprintln("read touched file error,err=", err.Error()) + } + return string(allBytes) +} + +func GetNetInfos() string { + resp, err := http.DefaultClient.Get("https://www.baidu.com") + if err != nil { + return err.Error() + } + htmlBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err.Error() + } + return string(htmlBytes) +} + +func RunLoop() string { + for { + <-time.After(2 * time.Second) + fmt.Println("1") + } +} + +var backgroundLoopState = "" + +func BackgroundLoop() string { + if len(backgroundLoopState) != 0 { + fmt.Println("BackgroundLoop running already") + return "BackgroundLoop running" + } + backgroundLoopState = "123" + go func() { + for { + <-time.After(2 * time.Second) + fmt.Println("alive") + } + }() + return "OK" +} + +func GetWebSocketConnect() string { + var sw strings.Builder + conn, connResp, err := websocket.DefaultDialer.Dial("ws://54.175.247.94:20013", nil) + if err != nil { + return fmt.Sprintf("dial error,err=%+v \r\n", err) + } + defer conn.Close() + connRespBytes, err := ioutil.ReadAll(connResp.Body) + if err != nil { + return fmt.Sprintf("connresp return error,err=%+v", err) + } + sw.WriteString(string(connRespBytes)) + err = conn.WriteMessage(websocket.TextMessage, []byte(`{ + "jsonrpc":"2.0", + "method":"eth_subscribe", + "params":["newHeads"], + "id":1 +}`)) + if err != nil { + return fmt.Sprintf("connresp write message error,err=%+v", err) + } + + _, msg, err := conn.ReadMessage() + fmt.Println(string(msg)) + if err != nil { + sw.WriteString("read message error,err=" + err.Error()) + } + sw.WriteString("received message:" + string(msg)) + + go func() { + innerConn, _, err := websocket.DefaultDialer.Dial("ws://174.129.114.74:8546", nil) + if err != nil { + fmt.Printf("bg dial error,err=%+v \r\n", err) + return + } + defer innerConn.Close() + err = innerConn.WriteMessage(websocket.TextMessage, []byte(`{ + "jsonrpc":"2.0", + "method":"eth_subscribe", + "params":["newHeads"], + "id":1 + }`)) + if err != nil { + fmt.Printf("bg writemsg error,err=%+v \r\n", err) + return + } + for { + fmt.Println("bg readmsg:waitting") + _, msg, err := innerConn.ReadMessage() + if err != nil { + fmt.Println("bg readmsg error,err=" + err.Error()) + return + } + fmt.Println("bg readed msg:" + string(msg)) + } + }() + + return sw.String() +} + +func BlsTest() string { + var sw strings.Builder + + var err error + err = bls.TestSignVerify2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestAggregateVerify2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestAggregateVerify_CompressedSignatures2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestFastAggregateVerify2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestVerifyCompressed2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestMultipleSignatureVerification2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestFastAggregateVerify_ReturnsFalseOnEmptyPubKeyList2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestEth2FastAggregateVerify2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestEth2FastAggregateVerify_ReturnsFalseOnEmptyPubKeyList2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestEth2FastAggregateVerify_ReturnsTrueOnG2PointAtInfinity2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestSignatureFromBytes2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestMultipleSignatureFromBytes2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestCopy2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + err = bls.TestSecretKeyFromBytes2() + if err != nil { + sw.WriteString(err.Error() + "\r\n") + } + + sw.WriteString("bls test done.") + sw.WriteString("==============") + + return sw.String() +} diff --git a/cmd/evmsdk/common_test.go b/cmd/evmsdk/common_test.go new file mode 100644 index 0000000..48e405e --- /dev/null +++ b/cmd/evmsdk/common_test.go @@ -0,0 +1,429 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package evmsdk + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "testing" +) + +func TestGetNetInfos(t *testing.T) { + tests := []struct { + name string + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetNetInfos(); got != tt.want { + t.Errorf("GetNetInfos() = %v, want %v", got, tt.want) + } else { + t.Log(got) + fmt.Println(got) + } + }) + } +} + +//func TestEmit(t *testing.T) { +// type args struct { +// jsonText string +// } +// +// //settttting +// aa := Emit(`{"type":"test","val":{"app_base_path":"/Users/xan/misc"}}`) +// fmt.Println(aa) +// +// // tests := []struct { +// // name string +// // args args +// // }{{ +// // name: "t1", +// // args: args{ +// // jsonText: `{"type":"start"}`, +// // }, +// // }, { +// // name: "t2", +// // args: args{ +// // jsonText: `{"type":"test"}`, +// // }, +// // }, +// // // { +// // // name: "t3", +// // // args: args{ +// // // jsonText: `{"type":"list"}`, +// // // }, +// // // }, +// // } +// // for _, tt := range tests { +// // t.Run(tt.name, func(t *testing.T) { +// // got := Emit(tt.args.jsonText) +// // if len(got) == 0 { +// // t.Errorf("Emit() = %v", got) +// // } +// // t.Log(got) +// // fmt.Println(got) +// // }) +// // } +// +// // <-time.After(60 * time.Second) +//} + +func TestBlsSign(t *testing.T) { + pk := make([]byte, 32) + _, err := rand.Read(pk) + if err != nil { + t.Error(err) + } + + req := ` +{ + "type":"blssign", + "val":{ + "priv_key":"` + hex.EncodeToString(pk) + `", + "msg":"123123" + } +} + ` + resp := Emit(req) + _ = resp + fmt.Println(resp) +} + +//func TestEvmEngine_Start(t *testing.T) { +// type fields struct { +// mu sync.Mutex +// ctx context.Context +// cancelFunc context.CancelFunc +// Account string +// AppBasePath string +// State string +// BlockChan chan string +// PrivKey string +// ServerUri string +// } +// tests := []struct { +// name string +// fields fields +// wantErr bool +// }{ +// { +// name: "t1", +// fields: fields{ +// PrivKey: `b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSgodcFy6hKA9enyiAzEKfpg7Rib5AFx3w33V0NsMYOAoUXtbQtFIOIIgNsTBj9ei1DdGJ4QSbCgw3w37X7oXvkAAAAqAK68x8CuvMfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKCh1wXLqEoD16fKIDMQp+mDtGJvkAXHfDfdXQ2wxg4ChRe1tC0Ug4giA2xMGP16LUN0YnhBJsKDDfDftfuhe+QAAAAhAMhPG7U/g7k9+YWm66Yk1yDhUvwkHmLMWvV/bTtGeElWAAAACW1hY0Bib2dvbgECAwQFBg==`, +// ServerUri: "ws://127.0.0.1:20013", +// Account: "0x01", +// }, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// e := &EvmEngine{ +// mu: tt.fields.mu, +// ctx: tt.fields.ctx, +// cancelFunc: tt.fields.cancelFunc, +// Account: tt.fields.Account, +// AppBasePath: tt.fields.AppBasePath, +// EngineState: tt.fields.State, +// BlockChan: tt.fields.BlockChan, +// PrivKey: tt.fields.PrivKey, +// ServerUri: tt.fields.ServerUri, +// } +// if err := e.Start(); (err != nil) != tt.wantErr { +// t.Errorf("EvmEngine.Start() error = %v, wantErr %v", err, tt.wantErr) +// } +// }) +// } +//} + +//func TestEvmEngine_vertify(t *testing.T) { +// type fields struct { +// mu sync.Mutex +// ctx context.Context +// cancelFunc context.CancelFunc +// Account string +// AppBasePath string +// State string +// BlockChan chan string +// PrivKey string +// ServerUri string +// } +// type args struct { +// in []byte +// } +// tests := []struct { +// name string +// fields fields +// args args +// want []byte +// wantErr bool +// }{ +// { +// name: "t1", +// fields: fields{ +// PrivKey: `37d15846af852c47649005fd6dfe33483d394aaa60c14e7c56f4deadb116329e`, +// ServerUri: "ws://127.0.0.1:20013", +// Account: "0x01", +// }, +// args: args{ +// in: []byte(` +// { +// "entire": +// { +// "header":{ +// "parentHash":"0xeab8f2dca7682e72c594bfe9da4d83fb623741b21c729f57a7ff06a69aa3be38", +// "sha3Uncles":"0xeab8f2dca7682e72c594bfe9da4d83fb623741b21c729f57a7ff06a69aa3be38", +// "miner":"0x588639773Bc6F163aa262245CDa746c120676431", +// "stateRoot":"0xf9e3e616232713a12a90a2abc2302d5fd2b6360764c204ebe2acda342390841b", +// "transactionsRoot":"0xf9e3e616232713a12a90a2abc2302d5fd2b6360764c204ebe2acda342390841b", +// "receiptsRoot":"0xf9e3e616232713a12a90a2abc2302d5fd2b6360764c204ebe2acda342390841b", +// "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", +// "difficulty":"0x2a65b3", +// "number":"0x1532d9", +// "gasLimit":"0x1c9c380", +// "gasUsed": "0x0", +// "timestamp":"0x638f0e18", +// "extraData": "0xd883010a17846765746888676f312e31382e35856c696e757800000000000000ab9c084cefea3b41830c827c91b8908b12f64b08a243ee4fabd28bdd4556154f00d9a08f7c4a6c2794a63100d92910595e6747db4af69d576c224373c707db4400" +// }, +// "uncles":[], +// "transactions":[], +// "pprof":"0xf9e3e616232713a12a90a2abc2302d5fd2b6360764c204ebe2acda342390841b", +// "senders":[] +// }, +// "codes":[] +// }`), +// }, +// want: []byte("321123bb"), +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// e := &EvmEngine{ +// mu: tt.fields.mu, +// ctx: tt.fields.ctx, +// cancelFunc: tt.fields.cancelFunc, +// Account: tt.fields.Account, +// AppBasePath: tt.fields.AppBasePath, +// EngineState: tt.fields.State, +// BlockChan: tt.fields.BlockChan, +// PrivKey: tt.fields.PrivKey, +// ServerUri: tt.fields.ServerUri, +// } +// got, err := e.vertify(tt.args.in) +// if (err != nil) != tt.wantErr { +// t.Errorf("EvmEngine.vertify() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("EvmEngine.vertify() = %v, want %v", got, tt.want) +// } +// }) +// } +//} + +//func TestEngineStart(t *testing.T) { +// EE.Setting(&EmitRequest{ +// Typ: "setting", +// Val: map[string]interface{}{ +// "app_base_path": "/Users/xan/misc", +// "priv_key": "37d15846af852c47649005fd6dfe33483d394aaa60c14e7c56f4deadb116329e", //2d09d9f4e166f35a4ab0a2edd599e2a23bbe86b312b2e05b34d9fbe5693b1e48 +// "server_uri": "ws://127.0.0.1:20013", +// "account": "0x588639773bc6f163aa262245cda746c120676431", //0x7Ac869Ff8b6232f7cfC4370A2df4a81641Cba3d9 +// }, +// }) +// if err := EE.Start(); err != nil { +// t.Error(err) +// } +// select {} +//} + +/* +0xb1fd8daE392D8E7c5aA0c8D405EFC56e1F55c42c +732d6346fc020087ac84bd1a42adb444e0f783fe907759ba7fa977aab3bf66fc +*/ +//func TestEmitComplexEmulation(t *testing.T) { +// EE.Setting(&EmitRequest{ +// Typ: "setting", +// Val: map[string]interface{}{ +// "app_base_path": "/Users/mac/misc", +// "priv_key": "de4b76c3dca3d8e10aea7644f77b316a68a6476fbd119d441ead5c6131aa42a7", //2d09d9f4e166f35a4ab0a2edd599e2a23bbe86b312b2e05b34d9fbe5693b1e48 +// "server_uri": "ws://54.175.247.94:20013", +// "account": "0x65081DBA9E7B5398ec4d40f1794003c54dB11B79", //0x7Ac869Ff8b6232f7cfC4370A2df4a81641Cba3d9 +// }, +// }) +// if s := Emit(`{"type":"start"}`); len(s) != 0 { +// fmt.Println(s) +// } +// +// // i := 0 +// // for { +// // if s := Emit(`{"type":"state"}`); len(s) != 0 { +// // fmt.Println(s) +// // } +// // <-time.After(1 * time.Second) +// // if i%20 == 0 { +// // Emit(`{"type":"start"}`) +// // } +// // i++ +// // } +// +// select {} +//} + +//func TestEngineState(t *testing.T) { +// EE.Setting(&EmitRequest{ +// Typ: "setting", +// Val: map[string]interface{}{ +// "app_base_path": "/Users/xan/misc", +// "priv_key": "37d15846af852c47649005fd6dfe33483d394aaa60c14e7c56f4deadb116329e", +// "server_uri": "ws://127.0.0.1:20013", +// "account": "0x7Ac869Ff8b6232f7cfC4370A2df4a81641Cba3d9", +// }, +// }) +// if ss := EE.State(); ss != "stopped" { +// t.Error("unstopped") +// } +// if err := EE.Start(); err != nil { +// t.Error(err) +// } +// if ss := EE.State(); ss != "started" { +// t.Error("unstarted") +// } +// select {} +//} + +//func TestUnmarshalInput(t *testing.T) { +// EE.Setting(&EmitRequest{ +// Typ: "setting", +// Val: map[string]interface{}{ +// "app_base_path": "/Users/xan/misc", +// "priv_key": "37d15846af852c47649005fd6dfe33483d394aaa60c14e7c56f4deadb116329e", +// "server_uri": "ws://127.0.0.1:20013", +// "account": "0x7Ac869Ff8b6232f7cfC4370A2df4a81641Cba3d9", +// }, +// }) +// if ss := EE.State(); ss != "stopped" { +// t.Error("unstopped") +// } +// if err := EE.Start(); err != nil { +// t.Error(err) +// } +// if ss := EE.State(); ss != "started" { +// t.Error("unstarted") +// } +// select {} +//} + +func TestBlssign(t *testing.T) { + resp := Emit(` +{"type": "blssign","val": {"priv_key": "202cbf36864a88e348c8f573aa0bc79f5a7119e58251c3580bb08af70cb2dfed", "msg" : "8ac7230489e80000"}} + `) + _ = resp +} + +func TestEngineStop(t *testing.T) { + if err := EE.Stop(); err != nil { + t.Error(err) + } +} + +/* +{ + "jsonrpc": "2.0", + "method": "eth_subscription", + "params": { + "subscription": "0x7e6684b10d6c906c47253690b8e9c7fa", + "result": { + "Entire": { + "entire": { + "header": { + "parentHash": "0x76b286a66229e34455f5238b9f9c0d516903c222b4e5874a6662820e25dcde98", + "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x2", + "number": "0xd2", + "gasLimit": "0x18462971", + "gasUsed": "0x0", + "timestamp": "0x63a15be4", + "extraData": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": null, + "hash": "0x051510ec6c9998e61126d285169ed6b24114b27c264ce0e5dad5e0a7bbac2f42" + }, + "uncles": null, + "transactions": [], + "snap": { + "items": [ + { + "key": "uelEd/X4i16Noul+hQbW5PzwTls=", + "value": "CAEaEzB4NjgxNTVhNDM2NzZlMDAwMDAiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKiDF0kYBhvcjPJJ+fbLcxwPA5QC2U8qCJzt7+tgEXYWkcA==" + }, + { + "key": "m6M2g1Qiuu/FN9dWQpWdKoZlAKM=", + "value": "CAEaEzB4NjgxNTVhNDM2NzZlMDAwMDAiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKiDF0kYBhvcjPJJ+fbLcxwPA5QC2U8qCJzt7+tgEXYWkcA==" + }, + { + "key": "RUH8HMtOBCo7rf5GkE+dItEntoI=", + "value": "CAEaEzB4NjgxNTVhNDM2NzZlMDAwMDAiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKiDF0kYBhvcjPJJ+fbLcxwPA5QC2U8qCJzt7+tgEXYWkcA==" + } + ], + "outHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, + "proof": "0x0000000000000000000000000000000000000000000000000000000000000000", + "senders": null + }, + "codes": [], + "headers": [], + "rewards": { + "0x4541fc1ccb4e042a3badfe46904f9d22d127b682": { + "Int": "0x20f5b1eaad8d80000" + }, + "0x9ba336835422baefc537d75642959d2a866500a3": { + "Int": "0x1d7d843dc3b480000" + }, + "0xb9e94477f5f88b5e8da2e97e8506d6e4fcf04e5b": { + "Int": "0x1bc16d674ec800000" + } + } + } + } + } +} +*/ + +func TestBeanchMark(t *testing.T) { + + var ( + key, _ = crypto.HexToECDSA("d6d8d19bd786d6676819b806694b1100a4414a94e51e9a82a351bd8f7f3f3658") + addr = crypto.PubkeyToAddress(key.PublicKey) + //signer = new(types.HomesteadSigner) + //content = context.Background() + ) + + fmt.Println(addr) +} diff --git a/cmd/evmsdk/verify.go b/cmd/evmsdk/verify.go new file mode 100644 index 0000000..bb685b3 --- /dev/null +++ b/cmd/evmsdk/verify.go @@ -0,0 +1,140 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package evmsdk + +import ( + "context" + "fmt" + "github.com/astranetworld/ast/common/hexutil" + "unsafe" + + common2 "github.com/astranetworld/ast/common" + block2 "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal" + "github.com/astranetworld/ast/internal/consensus/apos" + "github.com/astranetworld/ast/internal/consensus/misc" + "github.com/astranetworld/ast/internal/vm" + "github.com/astranetworld/ast/modules/ethdb/olddb" + "github.com/astranetworld/ast/modules/state" + "github.com/astranetworld/ast/params" +) + +func verify(ctx context.Context, msg *state.EntireCode) types.Hash { + codeMap := make(map[types.Hash][]byte) + for _, pair := range msg.Codes { + codeMap[pair.Hash] = pair.Code + } + + readCodeF := func(hash types.Hash) ([]byte, error) { + if code, ok := codeMap[hash]; ok { + return code, nil + } + return nil, nil + } + + hashMap := make(map[uint64]types.Hash) + for _, h := range msg.Headers { + hashMap[h.Number.Uint64()] = h.Hash() + } + getNumberHash := func(n uint64) types.Hash { + if hash, ok := hashMap[n]; ok { + return hash + } + return types.Hash{} + } + + var txs transaction.Transactions + for _, tByte := range msg.Entire.Transactions { + tmp := &transaction.Transaction{} + if err := tmp.Unmarshal(tByte); nil != err { + panic(err) + } + txs = append(txs, tmp) + } + + body := &block2.Body{ + Txs: txs, + } + + block := block2.NewBlockFromStorage(msg.Entire.Header.Hash(), msg.Entire.Header, body) + batch := olddb.NewHashBatch(nil, ctx.Done(), "") + defer batch.Rollback() + old := make(map[string][]byte, len(msg.Entire.Snap.Items)) + for _, v := range msg.Entire.Snap.Items { + old[*(*string)(unsafe.Pointer(&v.Key))] = v.Value + } + stateReader := olddb.NewStateReader(old, nil, batch, block.Number64().Uint64()) + stateReader.SetReadCodeF(readCodeF) + ibs := state.New(stateReader) + ibs.SetSnapshot(msg.Entire.Snap) + ibs.SetHeight(block.Number64().Uint64()) + ibs.SetGetOneFun(batch.GetOne) + + root, err := checkBlock2(getNumberHash, block, ibs, msg.CoinBase, msg.Rewards) + if nil != err { + panic(err) + } + return root +} + +func checkBlock2(getHashF func(n uint64) types.Hash, block *block2.Block, ibs *state.IntraBlockState, coinbase types.Address, rewards []*block2.Reward) (types.Hash, error) { + header := block.Header().(*block2.Header) + chainConfig := params.MainnetChainConfig + if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number64().ToBig()) == 0 { + misc.ApplyDAOHardFork(ibs) + } + noop := state.NewNoopWriter() + + usedGas := new(uint64) + gp := new(common2.GasPool) + gp.AddGas(block.GasLimit()) + cfg := vm.Config{} + //cfg := vm.Config{Debug: true, Tracer: logger.NewMarkdownLogger(nil, os.Stdout)} + + engine := apos.NewFaker() + for i, tx := range block.Transactions() { + // + simpleLog("apply transaction", "transactionIndex", i, "from", tx.From().Hex(), "to", tx.To().Hex(), "value", tx.Value().Uint64(), "data", hexutil.Encode(tx.Data()), "gas", tx.Gas(), "gasPrice", tx.GasPrice().Uint64()) + // + ibs.Prepare(tx.Hash(), block.Hash(), i) + _, _, err := internal.ApplyTransaction(chainConfig, getHashF, engine, &coinbase, gp, ibs, noop, header, tx, usedGas, cfg) + if err != nil { + + return types.Hash{}, err + } + } + + if !cfg.StatelessExec && *usedGas != header.GasUsed { + return types.Hash{}, fmt.Errorf("gas used by execution: %d, in header: %d", *usedGas, header.GasUsed) + } + + if len(rewards) > 0 { + for _, reward := range rewards { + if reward.Amount != nil && !reward.Amount.IsZero() { + if !ibs.Exist(reward.Address) { + ibs.CreateAccount(reward.Address, false) + } + ibs.AddBalance(reward.Address, reward.Amount) + } + } + ibs.SoftFinalise() + } + + return ibs.IntermediateRoot(), nil +} diff --git a/cmd/evmsdk/ws.go b/cmd/evmsdk/ws.go new file mode 100644 index 0000000..990c5c3 --- /dev/null +++ b/cmd/evmsdk/ws.go @@ -0,0 +1,199 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package evmsdk + +import ( + "encoding/json" + "io/ioutil" + "runtime" + + "github.com/gorilla/websocket" +) + +type WebSocketService struct { + addr string + acc string + readConn *websocket.Conn + writeConn *websocket.Conn +} + +func NewWebSocketService(addr, acc string) (*WebSocketService, error) { + simpleLog("init ws read conn") + readConn, readResp, err := websocket.DefaultDialer.Dial(addr, nil) + if err != nil { + simpleLog("init ws read conn error,err=%+v", err) + return nil, err + } + if readResp != nil { + readRespBytes, err := ioutil.ReadAll(readResp.Body) + if err != nil { + simpleLog("readresp error", "err", err) + } + simpleLog("readresp", "readRespBytes", string(readRespBytes)) + } + readConn.SetPongHandler(func(appData string) error { simpleLog("read pong", appData); return nil }) + readConn.SetPingHandler(func(appData string) error { simpleLog("read ping", appData); return nil }) + simpleLog("init ws read conn") + + writeConn, writeResp, err := websocket.DefaultDialer.Dial(addr, nil) + if err != nil { + simpleLog("init ws write conn error,err=%+v", err) + return nil, err + } + if writeResp != nil { + readRespBytes, err := ioutil.ReadAll(writeResp.Body) + if err != nil { + simpleLog("writeresp error", "err", err) + } + simpleLog("writeresp", "writeRespBytes", string(readRespBytes)) + } + writeConn.SetPongHandler(func(appData string) error { simpleLog("write pong", appData); return nil }) + writeConn.SetPingHandler(func(appData string) error { simpleLog("write ping", appData); return nil }) + simpleLog("init dial ws done") + return &WebSocketService{ + addr: addr, + readConn: readConn, + writeConn: writeConn, + acc: acc, + }, nil +} + +func (ws *WebSocketService) Chans(pubk string) (<-chan []byte, chan<- []byte, error) { + simpleLog("ping read conn") + err := ws.readConn.PingHandler()("") + if err != nil { + simpleLog("ping read conn error,err=%+v", err) + return nil, nil, err + } + simpleLog("ping done") + msg := `{ + "jsonrpc": "2.0", + "method": "eth_subscribe", + "params": [ + "minedBlock", + "` + ws.acc + `" + ], + "id": 1 +}` + simpleLog("minedBlock message:%+v", msg) + + if err := ws.readConn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil { + simpleLog("minedBlock message write message error,err=%+v", err) + return nil, nil, err + } + + _, msg0, err := ws.readConn.ReadMessage() + if err != nil { + simpleLog("init read conn readmessage error,err=", err) + return nil, nil, err + } + msg0bean := new(JSONRPCRequest) + if err := json.Unmarshal(msg0, msg0bean); err != nil { + simpleLog("unmarshal message0 error,err=", err) + return nil, nil, err + } + if msg0bean.Error != nil { + if msgCode := msg0bean.Error["code"]; msgCode != nil { + if msgCodeFloat, ok := msgCode.(float64); ok && msgCodeFloat != 0 { + simpleLogf("init read conn error,code=%+v ,message=%+v", msgCodeFloat, msg0bean.Error["message"]) + return nil, nil, &InnerError{Code: int(msgCodeFloat), Msg: msg0bean.Error["message"].(string)} + } + } + } + + chO, chI := make(chan []byte, 0), make(chan []byte, 0) + go func() { + defer func() { + if err := recover(); err != nil { + buf := make([]byte, 4096) + runtime.Stack(buf, true) + simpleLog("readconn goroutine down", "err", err) + simpleLog(string(buf)) + } + EE.Stop() + }() + for { + msgType, b, err := ws.readConn.ReadMessage() + if err != nil { + simpleLog("ws closed:", err.Error()) + close(chO) + return + } + if msgType == websocket.TextMessage { + chO <- b + } + simpleLog("received msg:", string(b)) + } + }() + go func() { + defer func() { + if err := recover(); err != nil { + buf := make([]byte, 4096) + runtime.Stack(buf, true) + simpleLog("writeconn goroutine down", "err", err) + simpleLog(string(buf)) + } + EE.Stop() + }() + for msg := range chI { + /* + { + "jsonrpc":"2.0", + "method":"eth_submitSign", + "params":[{ + "number":11111, + "stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000", + "sign":"0xb4dd42744e40aa50bc0e747a52dcf8d1196c08a0f9fcfd8fda0aaa413a47bbdda20d1a76298642ee68b1f1009df149680e4e4b14f8c488faa4010ad9f26a81379c93d7f0da5596192da203c31a6301cd50d53734e537d828fe7f9593eb7035d3", + "publicKey":"0xa8a236acd9b7ea68c1858a3059e4aad7698842f7de49ace8dc49acc922bd6d543792e98b7b74fa380bf622671792d57a" + }], + "id":1 + } + */ + wrappedRequest, err := ws.unwrapJSONRPCRequest(msg) + if err != nil { + simpleLog("wrapJSONRPCRequest error,err=", err) + continue + } + if err := ws.writeConn.WriteMessage(websocket.TextMessage, wrappedRequest); err != nil { + simpleLog("ws producer stopped") + return + } + simpleLog("submit Sign to chain") + } + }() + return chO, chI, nil +} + +type JSONRPCRequest struct { + JsonRpc string `json:"jsonrpc"` + Method string `json:"method"` + ID int `json:"id"` + Params []json.RawMessage `json:"params"` + Error map[string]interface{} `json:"error"` +} + +func (ws *WebSocketService) unwrapJSONRPCRequest(in []byte) ([]byte, error) { + d := &JSONRPCRequest{ + JsonRpc: "2.0", + Method: "eth_submitSign", + ID: 1, + Params: make([]json.RawMessage, 1), + } + d.Params[0] = in + return json.Marshal(d) + +} diff --git a/cmd/utils/prompt.go b/cmd/utils/prompt.go new file mode 100644 index 0000000..0d5b7df --- /dev/null +++ b/cmd/utils/prompt.go @@ -0,0 +1,59 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package utils + +import ( + "fmt" + "github.com/astranetworld/ast/console/prompt" +) + +// GetPassPhrase displays the given text(prompt) to the user and requests some textual +// data to be entered, but one which must not be echoed out into the terminal. +// The method returns the input provided by the user. +func GetPassPhrase(text string, confirmation bool) string { + if text != "" { + fmt.Println(text) + } + password, err := prompt.Stdin.PromptPassword("Password: ") + if err != nil { + Fatalf("Failed to read password: %v", err) + } + if confirmation { + confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") + if err != nil { + Fatalf("Failed to read password confirmation: %v", err) + } + if password != confirm { + Fatalf("Passwords do not match") + } + } + return password +} + +// GetPassPhraseWithList retrieves the password associated with an account, either fetched +// from a list of preloaded passphrases, or requested interactively from the user. +func GetPassPhraseWithList(text string, confirmation bool, index int, passwords []string) string { + // If a list of passwords was supplied, retrieve from them + if len(passwords) > 0 { + if index < len(passwords) { + return passwords[index] + } + return passwords[len(passwords)-1] + } + // Otherwise prompt the user for the password + password := GetPassPhrase(text, confirmation) + return password +} diff --git a/cmd/utils/tools.go b/cmd/utils/tools.go new file mode 100644 index 0000000..2fbb021 --- /dev/null +++ b/cmd/utils/tools.go @@ -0,0 +1,24 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +//go:build tools +// +build tools + +package tools + +import ( + // Tool imports for go:generate. + _ "github.com/fjl/gencodec" +) diff --git a/cmd/utils/utils.go b/cmd/utils/utils.go new file mode 100644 index 0000000..c6cb930 --- /dev/null +++ b/cmd/utils/utils.go @@ -0,0 +1,76 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package utils + +import ( + "fmt" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "io" + "os" + "runtime" + "strconv" + + "github.com/astranetworld/ast/accounts" + "github.com/astranetworld/ast/accounts/keystore" + "github.com/astranetworld/ast/internal/avm/common" +) + +// MakeAddress converts an account specified directly as a hex encoded string or +// a key index in the key store to an internal account representation. +func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) { + // If the specified account is a valid address, return it + if common.IsHexAddress(account) { + return accounts.Account{Address: types.Address(common.HexToAddress(account))}, nil + } + // Otherwise try to interpret the account as a keystore index + index, err := strconv.Atoi(account) + if err != nil || index < 0 { + return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) + } + log.Warn("-------------------------------------------------------------------") + log.Warn("Referring to accounts by order in the keystore folder is dangerous!") + log.Warn("This functionality is deprecated and will be removed in the future!") + log.Warn("Please use explicit addresses! (can search via `geth account list`)") + log.Warn("-------------------------------------------------------------------") + + accs := ks.Accounts() + if len(accs) <= index { + return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs)) + } + return accs[index], nil +} + +// Fatalf formats a message to standard error and exits the program. +// The message is also printed to standard output if standard error +// is redirected to a different file. +func Fatalf(format string, args ...interface{}) { + w := io.MultiWriter(os.Stdout, os.Stderr) + if runtime.GOOS == "windows" { + // The SameFile check below doesn't work on Windows. + // stdout is unlikely to get redirected though, so just print there. + w = os.Stdout + } else { + outf, _ := os.Stdout.Stat() + errf, _ := os.Stderr.Stat() + if outf != nil && errf != nil && os.SameFile(outf, errf) { + w = os.Stderr + } + } + fmt.Fprintf(w, "Fatal: "+format+"\n", args...) + os.Exit(1) +} diff --git a/cmd/verify/main.go b/cmd/verify/main.go new file mode 100644 index 0000000..11a45f2 --- /dev/null +++ b/cmd/verify/main.go @@ -0,0 +1,200 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/crypto/bls" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/api" + "github.com/astranetworld/ast/log" + "github.com/astranetworld/ast/modules/state" + "github.com/go-kit/kit/transport/http/jsonrpc" + "github.com/gorilla/websocket" + "os" + "os/signal" + "syscall" +) + +var privateKey bls.SecretKey +var addressKey types.Address + +func RootContext() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer cancel() + + ch := make(chan os.Signal, 1) + defer close(ch) + + signal.Notify(ch, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(ch) + + select { + case sig := <-ch: + log.Info("Got interrupt, shutting down...", "sig", sig) + case <-ctx.Done(): + } + }() + return ctx, cancel +} + +func main() { + var err error + sByte, err := hex.DecodeString("2d09d9f4e166f35a4ab0a2edd599e2a23bbe86b312b2e05b34d9fbe5693b1e48") + if nil != err { + panic(err) + } + + var sb [32]byte + copy(sb[:], sByte) + privateKey, err = bls.SecretKeyFromRandom32Byte(sb) + if nil != err { + panic(err) + } + + ecdPk, err := crypto.HexToECDSA("2d09d9f4e166f35a4ab0a2edd599e2a23bbe86b312b2e05b34d9fbe5693b1e48") + if nil != err { + panic(err) + } + addressKey = crypto.PubkeyToAddress(ecdPk.PublicKey) + + ctx, cancle := RootContext() + defer cancle() + + con, _, err := websocket.DefaultDialer.DialContext(ctx, "ws://127.0.0.1:20013", nil) + defer con.Close() + + end := make(chan struct{}) + defer close(end) + + go func() { + for { + select { + case <-ctx.Done(): + end <- struct{}{} + return + default: + typ, msg, err := con.ReadMessage() + if nil != err { + log.Errorf("read msg failed: %v", err) + continue + } + if typ == websocket.TextMessage { + fmt.Println("read msg: ", string(msg)) + params, err := unwrapJSONRPC(msg) + if nil != err { + log.Warn(err.Error()) + continue + } + + bean := new(state.EntireCode) + if err := json.Unmarshal(params, bean); err != nil { + log.Errorf("unmarshal entire failed, %v", err) + continue + } + + root := verify(ctx, bean) + res := api.AggSign{} + res.Number = bean.Entire.Header.Number.Uint64() + res.Address = addressKey + res.StateRoot = root + copy(res.Sign[:], privateKey.Sign(root[:]).Marshal()) + in, err := json.Marshal(res) + if nil != err { + panic(err) + } + + wrapRequest, _ := wrapJSONRPCRequest(in) + if err := con.WriteMessage(websocket.TextMessage, wrapRequest); nil != err { + log.Error("write msg failed: ", err) + } + log.Infof("write msg: %s", wrapRequest) + } + } + } + }() + + if err = con.PingHandler()(""); nil != err { + panic(err) + } + + if err := con.WriteMessage(websocket.TextMessage, []byte(`{ + "jsonrpc": "2.0", + "method": "eth_subscribe", + "params": [ + "minedBlock", + "`+addressKey.String()+`" + ], + "id": 1 + }`)); err != nil { + + cancle() + } + + <-end +} + +func unwrapJSONRPC(in []byte) ([]byte, error) { + //"{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32000,\"message\":\"unauthed address: 0xeB156a42dcaFcf155B07f3638892440C7dE5d564\"}}\n" + //ws consumer received msg:%!(EXTRA string=ws consumer received msg:, string={"jsonrpc":"2.0","id":1,"result":"0x96410b68a9f8875bb20fde06823eb861"} + req := new(jsonrpc.Request) + if err := json.Unmarshal(in, req); err != nil { + return nil, err + } + if len(req.Params) == 0 { + return []byte{}, errors.New("empty request params") + } + + //type innerProtocolEntire struct { + // Entire json.RawMessage `json:"Entire"` + //} + type innerProtocol struct { + Subscription string `json:"subscription"` + Result json.RawMessage `json:"result"` + } + + innerReq := new(innerProtocol) + if err := json.Unmarshal(req.Params, innerReq); err != nil { + return nil, err + } + + return innerReq.Result, nil +} + +type JSONRPCRequest struct { + JsonRpc string `json:"jsonrpc"` + Method string `json:"method"` + ID int `json:"id"` + Params []json.RawMessage `json:"params"` +} + +func wrapJSONRPCRequest(in []byte) ([]byte, error) { + d := &JSONRPCRequest{ + JsonRpc: "2.0", + Method: "eth_submitSign", + ID: 1, + Params: make([]json.RawMessage, 1), + } + d.Params[0] = in + return json.Marshal(d) +} diff --git a/cmd/verify/verify.go b/cmd/verify/verify.go new file mode 100644 index 0000000..b89c518 --- /dev/null +++ b/cmd/verify/verify.go @@ -0,0 +1,135 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package main + +import ( + "context" + "fmt" + common2 "github.com/astranetworld/ast/common" + block2 "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal" + "github.com/astranetworld/ast/internal/consensus/apos" + "github.com/astranetworld/ast/internal/consensus/misc" + "github.com/astranetworld/ast/internal/vm" + "github.com/astranetworld/ast/modules/ethdb/olddb" + "github.com/astranetworld/ast/modules/state" + "github.com/astranetworld/ast/params" + "unsafe" +) + +func verify(ctx context.Context, msg *state.EntireCode) types.Hash { + codeMap := make(map[types.Hash][]byte) + for _, pair := range msg.Codes { + codeMap[pair.Hash] = pair.Code + } + + readCodeF := func(hash types.Hash) ([]byte, error) { + if code, ok := codeMap[hash]; ok { + return code, nil + } + return nil, nil + } + + hashMap := make(map[uint64]types.Hash) + for _, h := range msg.Headers { + hashMap[h.Number.Uint64()] = h.Hash() + } + getNumberHash := func(n uint64) types.Hash { + if hash, ok := hashMap[n]; ok { + return hash + } + return types.Hash{} + } + + var txs transaction.Transactions + for _, tByte := range msg.Entire.Transactions { + tmp := &transaction.Transaction{} + if err := tmp.Unmarshal(tByte); nil != err { + panic(err) + } + txs = append(txs, tmp) + } + + body := &block2.Body{ + Txs: txs, + } + + block := block2.NewBlockFromStorage(msg.Entire.Header.Hash(), msg.Entire.Header, body) + batch := olddb.NewHashBatch(nil, ctx.Done(), "") + defer batch.Rollback() + old := make(map[string][]byte, len(msg.Entire.Snap.Items)) + for _, v := range msg.Entire.Snap.Items { + old[*(*string)(unsafe.Pointer(&v.Key))] = v.Value + } + stateReader := olddb.NewStateReader(old, nil, batch, block.Number64().Uint64()) + stateReader.SetReadCodeF(readCodeF) + ibs := state.New(stateReader) + ibs.SetSnapshot(msg.Entire.Snap) + ibs.SetHeight(block.Number64().Uint64()) + ibs.SetGetOneFun(batch.GetOne) + + root, err := checkBlock(getNumberHash, block, ibs, msg.CoinBase, msg.Rewards) + if nil != err { + panic(err) + } + return root +} + +func checkBlock(getHashF func(n uint64) types.Hash, block *block2.Block, ibs *state.IntraBlockState, coinbase types.Address, rewards []*block2.Reward) (types.Hash, error) { + header := block.Header().(*block2.Header) + chainConfig := params.MainnetChainConfig + if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number64().ToBig()) == 0 { + misc.ApplyDAOHardFork(ibs) + } + noop := state.NewNoopWriter() + + usedGas := new(uint64) + gp := new(common2.GasPool) + gp.AddGas(block.GasLimit()) + cfg := vm.Config{} + //cfg := vm.Config{Debug: true, Tracer: logger.NewMarkdownLogger(nil, os.Stdout)} + + engine := apos.NewFaker() + for i, tx := range block.Transactions() { + ibs.Prepare(tx.Hash(), block.Hash(), i) + _, _, err := internal.ApplyTransaction(chainConfig, getHashF, engine, &coinbase, gp, ibs, noop, header, tx, usedGas, cfg) + if err != nil { + + return types.Hash{}, err + } + } + + if !cfg.StatelessExec && *usedGas != header.GasUsed { + return types.Hash{}, fmt.Errorf("gas used by execution: %d, in header: %d", *usedGas, header.GasUsed) + } + + if len(rewards) > 0 { + for _, reward := range rewards { + if reward.Amount != nil && !reward.Amount.IsZero() { + if !ibs.Exist(reward.Address) { + ibs.CreateAccount(reward.Address, false) + } + ibs.AddBalance(reward.Address, reward.Amount) + } + } + ibs.SoftFinalise() + } + + return ibs.IntermediateRoot(), nil +} diff --git a/common/account/state_account.go b/common/account/state_account.go new file mode 100644 index 0000000..29ef3cf --- /dev/null +++ b/common/account/state_account.go @@ -0,0 +1,682 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package account + +import ( + "fmt" + "github.com/astranetworld/ast/api/protocol/state" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" +) + +// Account is the Ethereum consensus representation of accounts. +// These objects are stored in the main account trie. +// DESCRIBED: docs/programmers_guide/guide.md#ethereum-state +type StateAccount struct { + Initialised bool + Nonce uint64 + Balance uint256.Int + Root types.Hash + CodeHash types.Hash // hash of the bytecode + Incarnation uint16 +} + +const ( + MimetypeDataWithValidator = "data/validator" + MimetypeTypedData = "data/typed" + MimetypeClique = "application/x-clique-header" + MimetypeParlia = "application/x-parlia-header" + MimetypeBor = "application/x-bor-header" + MimetypeTextPlain = "text/plain" +) + +var emptyCodeHash = crypto.Keccak256Hash(nil) + +// var emptyRoot = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") +var emptyRoot = types.BytesHash(crypto.Keccak256(nil)) + +// NewAccount creates a new account w/o code nor storage. +func NewAccount() StateAccount { + return StateAccount{ + Root: emptyRoot, + CodeHash: emptyCodeHash, + } +} + +func (a *StateAccount) EncodingLengthForStorage() uint { + pb := a.ToProtoMessage() + return uint(proto.Size(pb)) +} + +// var structLength uint = 1 // 1 byte for fieldset +// +// if !a.Balance.IsZero() { +// structLength += uint(a.Balance.ByteLen()) + 1 +// } +// +// if a.Nonce > 0 { +// structLength += uint((bits.Len64(a.Nonce)+7)/8) + 1 +// } +// +// if !a.IsEmptyCodeHash() { +// structLength += 33 // 32-byte array + 1 bytes for length +// } +// +// if a.Incarnation > 0 { +// structLength += uint((bits.Len64(a.Incarnation)+7)/8) + 1 +// } +// +// return structLength +//} + +//func (a *Account) EncodingLengthForHashing() uint { +// var structLength uint +// +// balanceBytes := 0 +// if !a.Balance.LtUint64(128) { +// balanceBytes = a.Balance.ByteLen() +// } +// +// nonceBytes := rlp.IntLenExcludingHead(a.Nonce) +// +// structLength += uint(balanceBytes + nonceBytes + 2) +// +// structLength += 66 // Two 32-byte arrays + 2 prefixes +// +// if structLength < 56 { +// return 1 + structLength +// } +// +// lengthBytes := (bits.Len(structLength) + 7) / 8 +// +// return uint(1+lengthBytes) + structLength +//} + +func (a *StateAccount) EncodeForStorage(buffer []byte) { + pb := a.ToProtoMessage() + data, _ := proto.Marshal(pb) + copy(buffer, data) + //var fieldSet = 0 // start with first bit set to 0 + //var pos = 1 + //if a.Nonce > 0 { + // fieldSet = 1 + // nonceBytes := (bits.Len64(a.Nonce) + 7) / 8 + // buffer[pos] = byte(nonceBytes) + // var nonce = a.Nonce + // for i := nonceBytes; i > 0; i-- { + // buffer[pos+i] = byte(nonce) + // nonce >>= 8 + // } + // pos += nonceBytes + 1 + //} + // + //// Encoding balance + //if !a.Balance.IsZero() { + // fieldSet |= 2 + // balanceBytes := a.Balance.ByteLen() + // buffer[pos] = byte(balanceBytes) + // pos++ + // a.Balance.WriteToSlice(buffer[pos : pos+balanceBytes]) + // pos += balanceBytes + //} + // + //if a.Incarnation > 0 { + // fieldSet |= 4 + // incarnationBytes := (bits.Len64(uint64(a.Incarnation)) + 7) / 8 + // buffer[pos] = byte(incarnationBytes) + // var incarnation = a.Incarnation + // for i := incarnationBytes; i > 0; i-- { + // buffer[pos+i] = byte(incarnation) + // incarnation >>= 8 + // } + // pos += incarnationBytes + 1 + //} + // + //// Encoding CodeHash + //if !a.IsEmptyCodeHash() { + // fieldSet |= 8 + // buffer[pos] = 32 + // copy(buffer[pos+1:], a.CodeHash.Bytes()) + // //pos += 33 + //} + // + //buffer[0] = byte(fieldSet) +} + +// Decodes length and determines whether it corresponds to a structure of a byte array +//func decodeLengthForHashing(buffer []byte, pos int) (length int, structure bool, newPos int) { +// switch firstByte := int(buffer[pos]); { +// case firstByte < 128: +// return 0, false, pos +// case firstByte < 184: +// return firstByte - 128, false, pos + 1 +// case firstByte < 192: +// // Next byte is the length of the length + 183 +// lenEnd := pos + 1 + firstByte - 183 +// len := 0 +// for i := pos + 1; i < lenEnd; i++ { +// len = (len << 8) + int(buffer[i]) +// } +// return len, false, lenEnd +// case firstByte < 248: +// return firstByte - 192, true, pos + 1 +// default: +// // Next byte is the length of the length + 247 +// lenEnd := pos + 1 + firstByte - 247 +// len := 0 +// for i := pos + 1; i < lenEnd; i++ { +// len = (len << 8) + int(buffer[i]) +// } +// return len, true, lenEnd +// } +//} + +//var rlpEncodingBufPool = sync.Pool{ +// New: func() interface{} { +// buf := make([]byte, 0, 128) +// return &buf +// }, +//} + +//func (a *Account) EncodeRLP(w io.Writer) error { +// var buf []byte +// l := a.EncodingLengthForHashing() +// if l > 128 { +// buf = make([]byte, l) +// } else { +// bp := rlpEncodingBufPool.Get().(*[]byte) +// defer rlpEncodingBufPool.Put(bp) +// buf = *bp +// buf = buf[:l] +// } +// +// a.EncodeForHashing(buf) +// _, err := w.Write(buf) +// return err +//} + +//func (a *Account) EncodeForHashing(buffer []byte) { +// balanceBytes := 0 +// if !a.Balance.LtUint64(128) { +// balanceBytes = a.Balance.ByteLen() +// } +// +// nonceBytes := rlp.IntLenExcludingHead(a.Nonce) +// +// var structLength = uint(balanceBytes + nonceBytes + 2) +// structLength += 66 // Two 32-byte arrays + 2 prefixes +// +// var pos int +// if structLength < 56 { +// buffer[0] = byte(192 + structLength) +// pos = 1 +// } else { +// lengthBytes := (bits.Len(structLength) + 7) / 8 +// buffer[0] = byte(247 + lengthBytes) +// +// for i := lengthBytes; i > 0; i-- { +// buffer[i] = byte(structLength) +// structLength >>= 8 +// } +// +// pos = lengthBytes + 1 +// } +// +// // Encoding nonce +// if a.Nonce < 128 && a.Nonce != 0 { +// buffer[pos] = byte(a.Nonce) +// } else { +// buffer[pos] = byte(128 + nonceBytes) +// var nonce = a.Nonce +// for i := nonceBytes; i > 0; i-- { +// buffer[pos+i] = byte(nonce) +// nonce >>= 8 +// } +// } +// pos += 1 + nonceBytes +// +// // Encoding balance +// if a.Balance.LtUint64(128) && !a.Balance.IsZero() { +// buffer[pos] = byte(a.Balance.Uint64()) +// pos++ +// } else { +// buffer[pos] = byte(128 + balanceBytes) +// pos++ +// a.Balance.WriteToSlice(buffer[pos : pos+balanceBytes]) +// pos += balanceBytes +// } +// +// // Encoding Root and CodeHash +// buffer[pos] = 128 + 32 +// pos++ +// copy(buffer[pos:], a.Root[:]) +// pos += 32 +// buffer[pos] = 128 + 32 +// pos++ +// copy(buffer[pos:], a.CodeHash[:]) +// //pos += 32 +//} + +// Copy makes `a` a full, independent (meaning that if the `image` changes in any way, it does not affect `a`) copy of the account `image`. +func (a *StateAccount) Copy(image *StateAccount) { + a.Initialised = image.Initialised + a.Nonce = image.Nonce + a.Balance.Set(&image.Balance) + copy(a.Root[:], image.Root[:]) + copy(a.CodeHash[:], image.CodeHash[:]) + a.Incarnation = image.Incarnation +} + +//func (a *Account) DecodeForHashing(enc []byte) error { +// length, structure, pos := decodeLengthForHashing(enc, 0) +// if pos+length != len(enc) { +// return fmt.Errorf( +// "malformed RLP for Account(%x): prefixLength(%d) + dataLength(%d) != sliceLength(%d)", +// enc, pos, length, len(enc)) +// } +// if !structure { +// return fmt.Errorf( +// "encoding of Account should be RLP struct, got byte array: %x", +// enc, +// ) +// } +// +// a.Initialised = true +// a.Nonce = 0 +// a.Balance.Clear() +// a.Root = emptyRoot +// a.CodeHash = emptyCodeHash +// if length == 0 && structure { +// return nil +// } +// +// if pos < len(enc) { +// nonceBytes, s, newPos := decodeLengthForHashing(enc, pos) +// if s { +// return fmt.Errorf( +// "encoding of Account.Nonce should be byte array, got RLP struct: %x", +// enc[pos:newPos+nonceBytes], +// ) +// } +// +// if newPos+nonceBytes > len(enc) { +// return fmt.Errorf( +// "malformed RLP for Account.Nonce(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)", +// enc[pos:newPos+nonceBytes], +// newPos-pos, nonceBytes, len(enc)-pos, +// ) +// } +// +// var nonce uint64 +// if nonceBytes == 0 && newPos == pos { +// nonce = uint64(enc[newPos]) +// pos = newPos + 1 +// } else { +// for _, b := range enc[newPos : newPos+nonceBytes] { +// nonce = (nonce << 8) + uint64(b) +// } +// pos = newPos + nonceBytes +// } +// +// a.Nonce = nonce +// } +// if pos < len(enc) { +// balanceBytes, s, newPos := decodeLengthForHashing(enc, pos) +// if s { +// return fmt.Errorf( +// "encoding of Account.Balance should be byte array, got RLP struct: %x", +// enc[pos:newPos+balanceBytes], +// ) +// } +// +// if newPos+balanceBytes > len(enc) { +// return fmt.Errorf("malformed RLP for Account.Balance(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)", +// enc[pos], +// newPos-pos, balanceBytes, len(enc)-pos, +// ) +// } +// +// if balanceBytes == 0 && newPos == pos { +// a.Balance.SetUint64(uint64(enc[newPos])) +// pos = newPos + 1 +// } else { +// a.Balance.SetBytes(enc[newPos : newPos+balanceBytes]) +// pos = newPos + balanceBytes +// } +// } +// if pos < len(enc) { +// rootBytes, s, newPos := decodeLengthForHashing(enc, pos) +// if s { +// return fmt.Errorf( +// "encoding of Account.Root should be byte array, got RLP struct: %x", +// enc[pos:newPos+rootBytes], +// ) +// } +// +// if rootBytes != 32 { +// return fmt.Errorf( +// "encoding of Account.Root should have size 32, got %d", +// rootBytes, +// ) +// } +// +// if newPos+rootBytes > len(enc) { +// return fmt.Errorf("malformed RLP for Account.Root(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)", +// enc[pos], +// newPos-pos, rootBytes, len(enc)-pos, +// ) +// } +// +// copy(a.Root[:], enc[newPos:newPos+rootBytes]) +// pos = newPos + rootBytes +// } +// +// if pos < len(enc) { +// codeHashBytes, s, newPos := decodeLengthForHashing(enc, pos) +// if s { +// return fmt.Errorf( +// "encoding of Account.CodeHash should be byte array, got RLP struct: %x", +// enc[pos:newPos+codeHashBytes], +// ) +// } +// +// if codeHashBytes != 32 { +// return fmt.Errorf( +// "encoding of Account.CodeHash should have size 32, got %d", +// codeHashBytes, +// ) +// } +// +// if newPos+codeHashBytes > len(enc) { +// return fmt.Errorf("malformed RLP for Account.CodeHash(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)", +// enc[pos:newPos+codeHashBytes], +// newPos-pos, codeHashBytes, len(enc)-pos, +// ) +// } +// +// copy(a.CodeHash[:], enc[newPos:newPos+codeHashBytes]) +// pos = newPos + codeHashBytes +// } +// +// if pos < len(enc) { +// storageSizeBytes, s, newPos := decodeLengthForHashing(enc, pos) +// if s { +// return fmt.Errorf( +// "encoding of Account.StorageSize should be byte array, got RLP struct: %x", +// enc[pos:newPos+storageSizeBytes], +// ) +// } +// +// if newPos+storageSizeBytes > len(enc) { +// return fmt.Errorf( +// "malformed RLP for Account.StorageSize(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)", +// enc[pos:newPos+storageSizeBytes], +// newPos-pos, storageSizeBytes, len(enc)-pos, +// ) +// } +// +// // Commented out because of the ineffectual assignment - uncomment if adding more fields +// var storageSize uint64 +// if storageSizeBytes == 0 && newPos == pos { +// storageSize = uint64(enc[newPos]) +// pos = newPos + 1 +// } else { +// for _, b := range enc[newPos : newPos+storageSizeBytes] { +// storageSize = (storageSize << 8) + uint64(b) +// } +// pos = newPos + storageSizeBytes +// } +// _ = storageSize +// } +// _ = pos +// +// return nil +//} + +func (a *StateAccount) Reset() { + a.Initialised = true + a.Nonce = 0 + a.Incarnation = 0 + a.Balance.Clear() + copy(a.Root[:], emptyRoot[:]) + copy(a.CodeHash[:], emptyCodeHash[:]) +} + +func (a *StateAccount) DecodeForStorage(enc []byte) error { + a.Reset() + if len(enc) == 0 { + return nil + } + return a.Unmarshal(enc) + //pbAccount := new(state.Account) + //if err := proto.Unmarshal(enc, pbAccount); nil != err { + // return err + //} + //if err := a.FromProtoMessage(pbAccount); nil != err { + // return err + //} + + //a.Reset() + // + //if len(enc) == 0 { + // return nil + //} + // + //var fieldSet = enc[0] + //var pos = 1 + // + //if fieldSet&1 > 0 { + // decodeLength := int(enc[pos]) + // + // if len(enc) < pos+decodeLength+1 { + // return fmt.Errorf( + // "malformed CBOR for Account.Nonce: %s, Length %d", + // enc[pos+1:], decodeLength) + // } + // + // a.Nonce = bytesToUint64(enc[pos+1 : pos+decodeLength+1]) + // pos += decodeLength + 1 + //} + // + //if fieldSet&2 > 0 { + // decodeLength := int(enc[pos]) + // + // if len(enc) < pos+decodeLength+1 { + // return fmt.Errorf( + // "malformed CBOR for Account.Nonce: %s, Length %d", + // enc[pos+1:], decodeLength) + // } + // + // a.Balance.SetBytes(enc[pos+1 : pos+decodeLength+1]) + // pos += decodeLength + 1 + //} + // + //if fieldSet&4 > 0 { + // decodeLength := int(enc[pos]) + // + // if len(enc) < pos+decodeLength+1 { + // return fmt.Errorf( + // "malformed CBOR for Account.Incarnation: %s, Length %d", + // enc[pos+1:], decodeLength) + // } + // + // a.Incarnation = uint16(bytesToUint64(enc[pos+1 : pos+decodeLength+1])) + // pos += decodeLength + 1 + //} + // + //if fieldSet&8 > 0 { + // + // decodeLength := int(enc[pos]) + // + // if decodeLength != 32 { + // return fmt.Errorf("codehash should be 32 bytes long, got %d instead", + // decodeLength) + // } + // + // if len(enc) < pos+decodeLength+1 { + // return fmt.Errorf( + // "malformed CBOR for Account.CodeHash: %s, Length %d", + // enc[pos+1:], decodeLength) + // } + // + // a.CodeHash.SetBytes(enc[pos+1 : pos+decodeLength+1]) + // pos += decodeLength + 1 + //} + // + //_ = pos +} +func bytesToUint64(buf []byte) (x uint64) { + for i, b := range buf { + x = x<<8 + uint64(b) + if i == 7 { + return + } + } + return +} + +//func DecodeIncarnationFromStorage(enc []byte) (uint64, error) { +// if len(enc) == 0 { +// return 0, nil +// } +// +// var fieldSet = enc[0] +// var pos = 1 +// +// //looks for the position incarnation is at +// if fieldSet&1 > 0 { +// decodeLength := int(enc[pos]) +// if len(enc) < pos+decodeLength+1 { +// return 0, fmt.Errorf( +// "malformed CBOR for Account.Nonce: %s, Length %d", +// enc[pos+1:], decodeLength) +// } +// pos += decodeLength + 1 +// } +// +// if fieldSet&2 > 0 { +// decodeLength := int(enc[pos]) +// if len(enc) < pos+decodeLength+1 { +// return 0, fmt.Errorf( +// "malformed CBOR for Account.Nonce: %s, Length %d", +// enc[pos+1:], decodeLength) +// } +// pos += decodeLength + 1 +// } +// +// if fieldSet&4 > 0 { +// decodeLength := int(enc[pos]) +// +// //checks if the ending position is correct if not returns 0 +// if len(enc) < pos+decodeLength+1 { +// return 0, fmt.Errorf( +// "malformed CBOR for Account.Incarnation: %s, Length %d", +// enc[pos+1:], decodeLength) +// } +// +// incarnation := bytesToUint64(enc[pos+1 : pos+decodeLength+1]) +// return incarnation, nil +// } +// +// return 0, nil +// +//} + +func (a *StateAccount) SelfCopy() *StateAccount { + newAcc := NewAccount() + newAcc.Copy(a) + return &newAcc +} + +func (a *StateAccount) IsEmptyCodeHash() bool { + return IsEmptyCodeHash(a.CodeHash) +} + +func IsEmptyCodeHash(codeHash types.Hash) bool { + return codeHash == emptyCodeHash || codeHash == (types.Hash{}) +} + +func (a *StateAccount) IsEmptyRoot() bool { + return a.Root == emptyRoot || a.Root == types.Hash{} +} + +func (a *StateAccount) GetIncarnation() uint16 { + return a.Incarnation +} + +func (a *StateAccount) SetIncarnation(v uint16) { + a.Incarnation = v +} + +func (a *StateAccount) Equals(acc *StateAccount) bool { + return a.Nonce == acc.Nonce && + a.CodeHash == acc.CodeHash && + a.Balance.Cmp(&acc.Balance) == 0 && + a.Incarnation == acc.Incarnation +} + +func (a *StateAccount) Marshal() ([]byte, error) { + protoMsg := a.ToProtoMessage() + v, err := proto.Marshal(protoMsg) + if nil != err { + return nil, err + } + return v, nil +} + +func (a *StateAccount) Unmarshal(v []byte) error { + var pAccount state.Account + if err := proto.Unmarshal(v, &pAccount); nil != err { + return err + } + a.Initialised = pAccount.Initialised + a.Nonce = pAccount.Nonce + a.Balance = *utils.ConvertH256ToUint256Int(pAccount.Balance) + a.Root = utils.ConvertH256ToHash(pAccount.Root) + a.CodeHash = utils.ConvertH256ToHash(pAccount.CodeHash) + a.Incarnation = uint16(pAccount.Incarnation) + return nil +} + +func (a *StateAccount) ToProtoMessage() proto.Message { + return &state.Account{ + Initialised: a.Initialised, + Nonce: a.Nonce, + Balance: utils.ConvertUint256IntToH256(&a.Balance), + Root: utils.ConvertHashToH256(a.Root), + CodeHash: utils.ConvertHashToH256(a.CodeHash), + Incarnation: uint64(a.Incarnation), + } +} + +func (a *StateAccount) FromProtoMessage(msg proto.Message) error { + pAccount, ok := msg.(*state.Account) + if !ok { + return fmt.Errorf("impossible type assert ") + } + + a.Initialised = pAccount.Initialised + a.Nonce = pAccount.Nonce + a.Balance = *utils.ConvertH256ToUint256Int(pAccount.Balance) + a.Root = utils.ConvertH256ToHash(pAccount.Root) + a.CodeHash = utils.ConvertH256ToHash(pAccount.CodeHash) + a.Incarnation = uint16(pAccount.Incarnation) + return nil +} diff --git a/common/big.go b/common/big.go new file mode 100644 index 0000000..02c0ba5 --- /dev/null +++ b/common/big.go @@ -0,0 +1,29 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import "math/big" + +var ( + Big0 = big.NewInt(0) + Big1 = big.NewInt(1) + Big2 = big.NewInt(2) + Big3 = big.NewInt(3) + Big32 = big.NewInt(32) + Big256 = big.NewInt(256) + Big257 = big.NewInt(257) +) diff --git a/common/block/block.go b/common/block/block.go new file mode 100644 index 0000000..b4f6a0f --- /dev/null +++ b/common/block/block.go @@ -0,0 +1,270 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "fmt" + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/hash" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" + "strings" + "sync/atomic" + "time" +) + +//type writeCounter types.StorageSize +// +//func (c *writeCounter) Write(b []byte) (int, error) { +// *c += writeCounter(len(b)) +// return len(b), nil +//} + +type Block struct { + header *Header + body *Body + + hash atomic.Value + size atomic.Value + + ReceiveAt time.Time + ReceivedFrom interface{} +} + +type Verify struct { + Address types.Address + PublicKey types.PublicKey +} + +func (v *Verify) ToProtoMessage() proto.Message { + var pbVerifier types_pb.Verifier + pbVerifier.Address = utils.ConvertAddressToH160(v.Address) + pbVerifier.PublicKey = utils.ConvertPublicKeyToH384(v.PublicKey) + return &pbVerifier +} + +func (v *Verify) FromProtoMessage(pbVerifier *types_pb.Verifier) *Verify { + v.Address = utils.ConvertH160toAddress(pbVerifier.Address) + v.PublicKey = utils.ConvertH384ToPublicKey(pbVerifier.PublicKey) + return v +} + +type Reward struct { + Address types.Address + Amount *uint256.Int +} + +type Rewards []*Reward + +func (r Rewards) Len() int { + return len(r) +} + +func (r Rewards) Less(i, j int) bool { + return strings.Compare(r[i].Address.String(), r[j].Address.String()) > 0 +} + +func (r Rewards) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r *Reward) ToProtoMessage() proto.Message { + var pbReward types_pb.Reward + pbReward.Address = utils.ConvertAddressToH160(r.Address) + pbReward.Amount = utils.ConvertUint256IntToH256(r.Amount) + return &pbReward +} + +func (r *Reward) FromProtoMessage(pbReward *types_pb.Reward) *Reward { + r.Address = utils.ConvertH160toAddress(pbReward.Address) + r.Amount = utils.ConvertH256ToUint256Int(pbReward.Amount) + return r +} + +func (b *Block) Transactions() []*transaction.Transaction { + if b.body != nil { + return b.body.Transactions() + } + + return nil +} + +func (b *Block) StateRoot() types.Hash { + return b.header.Root +} + +func (b *Block) Hash() types.Hash { + return b.Header().Hash() +} + +func (b *Block) Marshal() ([]byte, error) { + bpBlock := b.ToProtoMessage() + return proto.Marshal(bpBlock) +} + +func (b *Block) Unmarshal(data []byte) error { + var pBlock types_pb.Block + if err := proto.Unmarshal(data, &pBlock); err != nil { + return err + } + if err := b.FromProtoMessage(&pBlock); err != nil { + return err + } + return nil +} + +// NewBlock creates a new block. The input data is copied, +// changes to header and to the field values will not affect the +// block. +// +// The values of TxHash, UncleHash, ReceiptHash and Bloom in header +// are ignored and set to values derived from the given txs, uncles +// and receipts. +func NewBlock(h IHeader, txs []*transaction.Transaction) IBlock { + + block := &Block{ + header: CopyHeader(h.(*Header)), + body: &Body{Txs: txs}, + ReceiveAt: time.Now(), + ReceivedFrom: nil, + } + return block +} + +func NewBlockFromReceipt(h IHeader, txs []*transaction.Transaction, uncles []IHeader, receipts []*Receipt, reward []*Reward) IBlock { + + block := &Block{ + header: CopyHeader(h.(*Header)), + body: &Body{Txs: txs, Rewards: CopyReward(reward)}, + ReceiveAt: time.Now(), + ReceivedFrom: nil, + } + //if len(receipts) > 0 { + // print(receipts) + //} + + block.header.Bloom = CreateBloom(receipts) + block.header.TxHash = hash.DeriveSha(transaction.Transactions(txs)) + block.header.ReceiptHash = hash.DeriveSha(Receipts(receipts)) + + return block +} + +func (b *Block) Header() IHeader { + return CopyHeader(b.header) +} + +func (b *Block) Body() IBody { + return b.body +} + +func (b *Block) Number64() *uint256.Int { + return b.header.Number +} + +func (b *Block) BaseFee64() *uint256.Int { + return b.header.BaseFee +} + +func (b *Block) Difficulty() *uint256.Int { + return b.header.Difficulty +} + +func (b *Block) Time() uint64 { + return b.header.Time +} + +func (b *Block) GasLimit() uint64 { + return b.header.GasLimit +} + +func (b *Block) GasUsed() uint64 { + return b.header.GasUsed +} + +func (b *Block) Nonce() uint64 { + return b.header.Nonce.Uint64() +} + +func (b *Block) Coinbase() types.Address { + return b.header.Coinbase +} + +func (b *Block) ParentHash() types.Hash { + return b.header.ParentHash +} + +func (b *Block) TxHash() types.Hash { + return b.header.TxHash +} + +func (b *Block) WithSeal(header IHeader) *Block { + b.header = CopyHeader(header.(*Header)) + return b +} + +func (b *Block) Transaction(hash types.Hash) *transaction.Transaction { + return nil +} + +func (b *Block) ToProtoMessage() proto.Message { + pbHeader := b.header.ToProtoMessage() + pbBody := b.body.ToProtoMessage() + pBlock := types_pb.Block{ + Header: pbHeader.(*types_pb.Header), + Body: pbBody.(*types_pb.Body), + } + + return &pBlock +} + +func (b *Block) FromProtoMessage(message proto.Message) error { + var ( + pBlock *types_pb.Block + header Header + body Body + ok bool + ) + + if pBlock, ok = message.(*types_pb.Block); !ok { + return fmt.Errorf("type conversion failure") + } + + if err := header.FromProtoMessage(pBlock.Header); err != nil { + return err + } + + if err := body.FromProtoMessage(pBlock.Body); err != nil { + return err + } + + b.header = &header + b.body = &body + b.ReceiveAt = time.Now() + return nil +} + +func (b *Block) SendersToTxs(senders []types.Address) { + //todo +} + +func (b *Block) Uncles() []*Header { + return nil +} diff --git a/common/block/block_test.go b/common/block/block_test.go new file mode 100644 index 0000000..bd6346f --- /dev/null +++ b/common/block/block_test.go @@ -0,0 +1,162 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "encoding/hex" + "encoding/json" + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/internal/avm/rlp" + "google.golang.org/protobuf/proto" + "testing" +) + +func getBlock(tb testing.TB) *Block { + //sBlock := "0af5010a200c138c091cfdedd52b8a6f35f664243525de3dba58260788a8e0c1806f3fd0911214f61c62199512e8e85d7744d513b1b74bbe98aea11a200000000000000000000000000000000000000000000000000000000000000000222000000000000000000000000000000000000000000000000000000000000000002a20000000000000000000000000000000000000000000000000000000000000000032033078313a0330783850dfe882950662033078306a40e6bf01023645789ba3cb7e8200eb54289bccbc292b4f4796d2268fee49a7a9a993c539ef50815018014438436b5e5a10c27a4ef2ea4dda9efe5ed5b30ab6fa061200" + sBlock := "0af5010a2009876b2d89375c505dd4128e2ea6bc588d8e9beb5ec11af94fc2f9d3e9eae5081214f61c62199512e8e85d7744d513b1b74bbe98aea11a20de7f0cbe30d14da2d6950c0643fe164b9e6ea421550263a02eaaaff257c2f6cc22208ac5c253af5118c7a98eecb957a37bf72a1f5556b8dfd1932322de5e10e32f312a2015647dcb388cef8e815a09408301361aed626048764adb507a06dc08ba593ae332033078323a0330783150858283950662033078306a4080e6d9a605d8be11339047b2541d7f59f3a193a0dad1e27360d016fba82c5449310b485172b8b2a705c8a8111c960149bccdd5c0ffea924c3fe6343aafd258021200" + bBlock, err := hex.DecodeString(sBlock) + if err != nil { + tb.Fatal(err) + } + + var block types_pb.Block + err = proto.Unmarshal(bBlock, &block) + if err != nil { + tb.Fatal(err) + } + + //tb.Logf("block number: %v", block.Header.Number.String()) + + var b Block + err = b.FromProtoMessage(&block) + if err != nil { + tb.Fatal(err) + } + + return &b +} + +type exBlock struct { + H *Header + B *Body +} + +//func TestSize(t *testing.T) { +// block := getBlock(t) +// pb := block.ToProtoMessage() +// +// buf1, err := proto.Marshal(pb) +// if err != nil { +// t.Fatal(err) +// } +// +// exB := exBlock{ +// H: block.header, +// B: block.body, +// } +// +// buf2, err := rlp.EncodeToBytes(&exB) +// if err != nil { +// t.Fatal(err) +// } +// +// t.Logf("proto size: %d, rlp size: %d", len(buf1), len(buf2)) +//} + +func BenchmarkProtobuf(b *testing.B) { + block := getBlock(b) + pb := block.ToProtoMessage() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = proto.Marshal(pb) + } +} + +func BenchmarkRlp(b *testing.B) { + block := getBlock(b) + exB := exBlock{ + H: block.header, + B: block.body, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = rlp.EncodeToBytes(&exB) + } +} + +func BenchmarkJson(b *testing.B) { + block := getBlock(b) + exB := exBlock{ + H: block.header, + B: block.body, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = json.Marshal(exB) + } +} + +func BenchmarkProtobufUnmarshal(b *testing.B) { + //sBlock := "0af5010a200c138c091cfdedd52b8a6f35f664243525de3dba58260788a8e0c1806f3fd0911214f61c62199512e8e85d7744d513b1b74bbe98aea11a200000000000000000000000000000000000000000000000000000000000000000222000000000000000000000000000000000000000000000000000000000000000002a20000000000000000000000000000000000000000000000000000000000000000032033078313a0330783850dfe882950662033078306a40e6bf01023645789ba3cb7e8200eb54289bccbc292b4f4796d2268fee49a7a9a993c539ef50815018014438436b5e5a10c27a4ef2ea4dda9efe5ed5b30ab6fa061200" + sBlock := "0af5010a2009876b2d89375c505dd4128e2ea6bc588d8e9beb5ec11af94fc2f9d3e9eae5081214f61c62199512e8e85d7744d513b1b74bbe98aea11a20de7f0cbe30d14da2d6950c0643fe164b9e6ea421550263a02eaaaff257c2f6cc22208ac5c253af5118c7a98eecb957a37bf72a1f5556b8dfd1932322de5e10e32f312a2015647dcb388cef8e815a09408301361aed626048764adb507a06dc08ba593ae332033078323a0330783150858283950662033078306a4080e6d9a605d8be11339047b2541d7f59f3a193a0dad1e27360d016fba82c5449310b485172b8b2a705c8a8111c960149bccdd5c0ffea924c3fe6343aafd258021200" + bBlock, err := hex.DecodeString(sBlock) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + var block types_pb.Block + _ = proto.Unmarshal(bBlock, &block) + } +} + +func BenchmarkRlpDecode(b *testing.B) { + block := getBlock(b) + exB := exBlock{ + H: block.header, + B: block.body, + } + + buf, err := rlp.EncodeToBytes(&exB) + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var exB exBlock + _ = rlp.DecodeBytes(buf, &exB) + } +} + +func BenchmarkJsonDecode(b *testing.B) { + block := getBlock(b) + exB := exBlock{ + H: block.header, + B: block.body, + } + bytes, err := json.Marshal(exB) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + var exB exBlock + _ = json.Unmarshal(bytes, &exB) + } +} diff --git a/common/block/bloom9.go b/common/block/bloom9.go new file mode 100644 index 0000000..636b415 --- /dev/null +++ b/common/block/bloom9.go @@ -0,0 +1,160 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package block + +import ( + "encoding/binary" + "fmt" + "math/big" + + "github.com/ledgerwatch/erigon-lib/common/hexutility" + + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/crypto/cryptopool" + "github.com/astranetworld/ast/common/hexutil" +) + +type bytesBacked interface { + Bytes() []byte +} + +const ( + // BloomByteLength represents the number of bytes used in a header log bloom. + BloomByteLength = 256 + + // BloomBitLength represents the number of bits used in a header log bloom. + BloomBitLength = 8 * BloomByteLength +) + +// Bloom represents a 2048 bit bloom filter. +type Bloom [BloomByteLength]byte + +// BytesToBloom converts a byte slice to a bloom filter. +// It panics if b is not of suitable size. +func BytesToBloom(b []byte) Bloom { + var bloom Bloom + bloom.SetBytes(b) + return bloom +} + +// SetBytes sets the content of b to the given bytes. +// It panics if d is not of suitable size. +func (b *Bloom) SetBytes(d []byte) { + if len(b) < len(d) { + panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d))) + } + copy(b[BloomByteLength-len(d):], d) +} + +// Add adds d to the filter. Future calls of Test(d) will return true. +func (b *Bloom) Add(d []byte) { + b.add(d, make([]byte, 6)) +} + +// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) +func (b *Bloom) add(d []byte, buf []byte) { + i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) + b[i1] |= v1 + b[i2] |= v2 + b[i3] |= v3 +} + +// Big converts b to a big integer. +// Note: Converting a bloom filter to a big.Int and then calling GetBytes +// does not return the same bytes, since big.Int will trim leading zeroes +func (b Bloom) Big() *big.Int { + return new(big.Int).SetBytes(b[:]) +} + +// Bytes returns the backing byte slice of the bloom +func (b Bloom) Bytes() []byte { + return b[:] +} + +// Test checks if the given topic is present in the bloom filter +func (b Bloom) Test(topic []byte) bool { + i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && + v2 == v2&b[i2] && + v3 == v3&b[i3] +} + +// MarshalText encodes b as a hex string with 0x prefix. +func (b Bloom) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +// UnmarshalText b as a hex string with 0x prefix. +func (b *Bloom) UnmarshalText(input []byte) error { + return hexutility.UnmarshalFixedText("Bloom", input, b[:]) +} + +func CreateBloom(receipts Receipts) Bloom { + buf := make([]byte, 6) + var bin Bloom + for _, receipt := range receipts { + for _, log := range receipt.Logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + } + return bin +} + +// LogsBloom returns the bloom bytes for the given logs +func LogsBloom(logs []*Log) []byte { + buf := make([]byte, 6) + var bin Bloom + for _, log := range logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + return bin[:] +} + +// Bloom9 returns the bloom filter for the given data +func Bloom9(data []byte) []byte { + var b Bloom + b.SetBytes(data) + return b.Bytes() +} + +// bloomValues returns the bytes (index-value pairs) to set for the given data +func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { + sha := crypto.NewKeccakState() + sha.Write(data) //nolint:errcheck + sha.Read(hashbuf) //nolint:errcheck + cryptopool.ReturnToPoolKeccak256(sha) + // The actual bits to flip + v1 := byte(1 << (hashbuf[1] & 0x7)) + v2 := byte(1 << (hashbuf[3] & 0x7)) + v3 := byte(1 << (hashbuf[5] & 0x7)) + // The indices for the bytes to OR in + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 + i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 + + return i1, v1, i2, v2, i3, v3 +} + +// BloomLookup is a convenience-method to check presence int he bloom filter +func BloomLookup(bin Bloom, topic bytesBacked) bool { + return bin.Test(topic.Bytes()) +} diff --git a/common/block/bloom9_test.go b/common/block/bloom9_test.go new file mode 100644 index 0000000..b5a0a6f --- /dev/null +++ b/common/block/bloom9_test.go @@ -0,0 +1,156 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package block + +import ( + "fmt" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + "testing" + + "github.com/astranetworld/ast/common/crypto" +) + +func TestBloom(t *testing.T) { + positive := []string{ + "testtest", + "test", + "hallo", + "other", + } + negative := []string{ + "tes", + "lo", + } + + var bloom Bloom + for _, data := range positive { + bloom.Add([]byte(data)) + } + + for _, data := range positive { + if !bloom.Test([]byte(data)) { + t.Error("expected", data, "to test true") + } + } + for _, data := range negative { + if bloom.Test([]byte(data)) { + t.Error("did not expect", data, "to test true") + } + } +} + +// TestBloomExtensively does some more thorough tests +func TestBloomExtensively(t *testing.T) { + var exp = types.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263") + var b Bloom + // Add 100 "random" things + for i := 0; i < 100; i++ { + data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i) + b.Add([]byte(data)) + //b.Add(new(big.Int).SetBytes([]byte(data))) + } + got := crypto.Keccak256Hash(b.Bytes()) + if got != exp { + t.Errorf("Got %x, exp %x", got, exp) + } + var b2 Bloom + b2.SetBytes(b.Bytes()) + got2 := crypto.Keccak256Hash(b2.Bytes()) + if got != got2 { + t.Errorf("Got %x, exp %x", got, got2) + } +} + +func BenchmarkBloom9(b *testing.B) { + test := []byte("testestestest") + for i := 0; i < b.N; i++ { + Bloom9(test) + } +} + +func BenchmarkBloom9Lookup(b *testing.B) { + toTest := []byte("testtest") + bloom := new(Bloom) + for i := 0; i < b.N; i++ { + bloom.Test(toTest) + } +} + +func BenchmarkCreateBloom(b *testing.B) { + to := types.HexToAddress("0x2") + var txs = transaction.Transactions{ + transaction.NewTx(&transaction.LegacyTx{Nonce: 1, Value: uint256.NewInt(1), Gas: 1, GasPrice: uint256.NewInt(1), Data: nil}), + transaction.NewTx(&transaction.LegacyTx{Nonce: 2, To: &to, Value: uint256.NewInt(2), Gas: 2, GasPrice: uint256.NewInt(2), Data: nil}), + } + var rSmall = Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + {Address: types.BytesToAddress([]byte{0x11})}, + {Address: types.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: txs[0].Hash(), + ContractAddress: types.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 1, + }, + &Receipt{ + PostState: types.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + {Address: types.BytesToAddress([]byte{0x22})}, + {Address: types.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: txs[1].Hash(), + ContractAddress: types.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 2, + }, + } + + var rLarge = make(Receipts, 200) + // Fill it with 200 receipts x 2 logs + for i := 0; i < 200; i += 2 { + copy(rLarge[i:], rSmall) + } + b.Run("small", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rSmall) + } + b.StopTimer() + var exp = types.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) + b.Run("large", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rLarge) + } + b.StopTimer() + var exp = types.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) +} diff --git a/common/block/body.go b/common/block/body.go new file mode 100644 index 0000000..7728108 --- /dev/null +++ b/common/block/body.go @@ -0,0 +1,171 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "fmt" + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/utils" + "google.golang.org/protobuf/proto" +) + +type Body struct { + Txs []*transaction.Transaction + Verifiers []*Verify + Rewards []*Reward +} + +func (b *Body) ToProtoMessage() proto.Message { + var pbTxs []*types_pb.Transaction + var pbVerifiers []*types_pb.Verifier + var pbRewards []*types_pb.Reward + + for _, v := range b.Txs { + pbTx := v.ToProtoMessage() + if pbTx != nil { + pbTxs = append(pbTxs, pbTx.(*types_pb.Transaction)) + } + } + + for _, reward := range b.Rewards { + pbReward := reward.ToProtoMessage() + if pbReward != nil { + pbRewards = append(pbRewards, pbReward.(*types_pb.Reward)) + } + } + + for _, verifier := range b.Verifiers { + pbVerifier := verifier.ToProtoMessage() + if pbVerifier != nil { + pbVerifiers = append(pbVerifiers, pbVerifier.(*types_pb.Verifier)) + } + } + + pBody := types_pb.Body{ + Txs: pbTxs, + Verifiers: pbVerifiers, + Rewards: pbRewards, + } + + return &pBody +} + +func (b *Body) FromProtoMessage(message proto.Message) error { + var ( + pBody *types_pb.Body + ok bool + ) + + if pBody, ok = message.(*types_pb.Body); !ok { + return fmt.Errorf("type conversion failure") + } + + var txs []*transaction.Transaction + // + for _, v := range pBody.Txs { + tx, err := transaction.FromProtoMessage(v) + if err != nil { + return err + } + txs = append(txs, tx) + } + // + b.Txs = txs + + //verifiers + var verifiers []*Verify + for _, v := range pBody.Verifiers { + verify := new(Verify).FromProtoMessage(v) + verifiers = append(verifiers, verify) + } + b.Verifiers = verifiers + + //Reward + var rewards []*Reward + for _, v := range pBody.Rewards { + reward := new(Reward).FromProtoMessage(v) + rewards = append(rewards, reward) + } + b.Rewards = rewards + + return nil +} + +func (b *Body) Transactions() []*transaction.Transaction { + return b.Txs +} +func (b *Body) Verifier() []*Verify { + return b.Verifiers +} + +func (b *Body) Reward() []*Reward { + return b.Rewards +} + +func (b *Body) reward() []*types_pb.H256 { + var rewardAmount []*types_pb.H256 + if len(b.Rewards) > 0 { + for _, reward := range b.Rewards { + rewardAmount = append(rewardAmount, utils.ConvertUint256IntToH256(reward.Amount)) + } + } + return rewardAmount +} + +func (b *Body) rewardAddress() []types.Address { + var rewardAddress []types.Address + for _, reward := range b.Rewards { + rewardAddress = append(rewardAddress, reward.Address) + } + return rewardAddress +} + +func (b *Body) SendersFromTxs() []types.Address { + senders := make([]types.Address, len(b.Transactions())) + for i, tx := range b.Transactions() { + senders[i] = *tx.From() + } + return senders +} + +func (b *Body) SendersToTxs(senders []types.Address) { + if senders == nil { + return + } + + //todo + //for i, tx := range b.Txs { + // //tx.SetFrom(senders[i]) + //} +} + +type BodyForStorage struct { + BaseTxId uint64 + TxAmount uint32 +} + +func NewBlockFromStorage(hash types.Hash, header *Header, body *Body) *Block { + b := &Block{header: header, body: body} + b.hash.Store(hash) + return b +} + +type RawBody struct { + Transactions [][]byte +} diff --git a/common/block/header.go b/common/block/header.go new file mode 100644 index 0000000..1da45d7 --- /dev/null +++ b/common/block/header.go @@ -0,0 +1,221 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" + "sync/atomic" + + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/types" +) + +type BlockNonce [8]byte + +// EncodeNonce converts the given integer to a block nonce. +func EncodeNonce(i uint64) BlockNonce { + var n BlockNonce + binary.BigEndian.PutUint64(n[:], i) + return n +} + +// Uint64 returns the integer value of a block nonce. +func (n BlockNonce) Uint64() uint64 { + return binary.BigEndian.Uint64(n[:]) +} + +// MarshalText todo copy eth +func (n BlockNonce) MarshalText() ([]byte, error) { + return hexutil.Bytes(n[:]).MarshalText() +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (n *BlockNonce) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) +} + +type Header struct { + ParentHash types.Hash `json:"parentHash" gencodec:"required"` + Coinbase types.Address `json:"miner"` + Root types.Hash `json:"stateRoot" gencodec:"required"` + TxHash types.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash types.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *uint256.Int `json:"difficulty" gencodec:"required"` + Number *uint256.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time uint64 `json:"timestamp" gencodec:"required"` + MixDigest types.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + Extra []byte `json:"extraData" gencodec:"required"` + + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *uint256.Int `json:"baseFeePerGas" rlp:"optional"` + + hash atomic.Value + + Signature types.Signature `json:"signature"` +} + +func (h *Header) Number64() *uint256.Int { + return h.Number +} + +func (h *Header) StateRoot() types.Hash { + return h.Root +} + +func (h *Header) BaseFee64() *uint256.Int { + return h.BaseFee +} + +func (h Header) Hash() types.Hash { + if hash := h.hash.Load(); hash != nil { + return hash.(types.Hash) + } + + if h.BaseFee == nil { + h.BaseFee = uint256.NewInt(0) + } + + if h.Difficulty == nil { + h.Difficulty = uint256.NewInt(0) + } + + buf, err := json.Marshal(h) + if err != nil { + return types.Hash{} + } + + if h.Number.Uint64() == 0 { + //log.Tracef("genesis header json Marshal: %s", string(buf)) + } + + hash := types.BytesHash(buf) + h.hash.Store(hash) + return hash +} + +func (h *Header) ToProtoMessage() proto.Message { + return &types_pb.Header{ + ParentHash: utils.ConvertHashToH256(h.ParentHash), + Coinbase: utils.ConvertAddressToH160(h.Coinbase), + Root: utils.ConvertHashToH256(h.Root), + TxHash: utils.ConvertHashToH256(h.TxHash), + ReceiptHash: utils.ConvertHashToH256(h.ReceiptHash), + Difficulty: utils.ConvertUint256IntToH256(h.Difficulty), + Number: utils.ConvertUint256IntToH256(h.Number), + GasLimit: h.GasLimit, + GasUsed: h.GasUsed, + Time: h.Time, + Nonce: h.Nonce.Uint64(), + BaseFee: utils.ConvertUint256IntToH256(h.BaseFee), + Extra: h.Extra, + Signature: utils.ConvertSignatureToH768(h.Signature), + Bloom: utils.ConvertBytesToH2048(h.Bloom.Bytes()), + MixDigest: utils.ConvertHashToH256(h.MixDigest), + } +} + +func (h *Header) FromProtoMessage(message proto.Message) error { + var ( + pbHeader *types_pb.Header + ok bool + ) + + if pbHeader, ok = message.(*types_pb.Header); !ok { + return fmt.Errorf("type conversion failure") + } + + h.ParentHash = utils.ConvertH256ToHash(pbHeader.ParentHash) + h.Coinbase = utils.ConvertH160toAddress(pbHeader.Coinbase) + h.Root = utils.ConvertH256ToHash(pbHeader.Root) + h.TxHash = utils.ConvertH256ToHash(pbHeader.TxHash) + h.ReceiptHash = utils.ConvertH256ToHash(pbHeader.ReceiptHash) + h.Difficulty = utils.ConvertH256ToUint256Int(pbHeader.Difficulty) + h.Number = utils.ConvertH256ToUint256Int(pbHeader.Number) + h.GasLimit = pbHeader.GasLimit + h.GasUsed = pbHeader.GasUsed + h.Time = pbHeader.Time + h.Nonce = EncodeNonce(pbHeader.Nonce) + h.BaseFee = utils.ConvertH256ToUint256Int(pbHeader.BaseFee) + h.Extra = pbHeader.Extra + h.Signature = utils.ConvertH768ToSignature(pbHeader.Signature) + h.Bloom = utils.ConvertH2048ToBloom(pbHeader.Bloom) + h.MixDigest = utils.ConvertH256ToHash(pbHeader.MixDigest) + return nil +} + +func (h *Header) Marshal() ([]byte, error) { + pbHeader := h.ToProtoMessage() + return proto.Marshal(pbHeader) +} + +func (h *Header) Unmarshal(data []byte) error { + var pbHeader types_pb.Header + if err := proto.Unmarshal(data, &pbHeader); err != nil { + return err + } + if err := h.FromProtoMessage(&pbHeader); err != nil { + return err + } + return nil +} + +func CopyHeader(h *Header) *Header { + cpy := *h + + if cpy.Difficulty = uint256.NewInt(0); h.Difficulty != nil { + cpy.Difficulty.SetBytes(h.Difficulty.Bytes()) + } + if cpy.Number = uint256.NewInt(0); h.Number != nil { + cpy.Number.SetBytes(h.Number.Bytes()) + } + + if h.BaseFee != nil { + cpy.BaseFee = uint256.NewInt(0).SetBytes(h.BaseFee.Bytes()) + } + + if len(h.Extra) > 0 { + cpy.Extra = make([]byte, len(h.Extra)) + copy(cpy.Extra, h.Extra) + } + return &cpy +} + +func CopyReward(rewards []*Reward) []*Reward { + var cpyReward []*Reward + + for _, reward := range rewards { + addr, amount := types.Address{}, uint256.Int{} + + addr.SetBytes(reward.Address[:]) + cpyReward = append(cpyReward, &Reward{ + Address: addr, + Amount: amount.SetBytes(reward.Amount.Bytes()), + }) + } + + return cpyReward +} diff --git a/common/block/iblock.go b/common/block/iblock.go new file mode 100644 index 0000000..d930db4 --- /dev/null +++ b/common/block/iblock.go @@ -0,0 +1,65 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" +) + +type IHeader interface { + Number64() *uint256.Int + BaseFee64() *uint256.Int + Hash() types.Hash + ToProtoMessage() proto.Message + FromProtoMessage(message proto.Message) error + Marshal() ([]byte, error) + Unmarshal(data []byte) error + StateRoot() types.Hash +} + +type IBody interface { + Verifier() []*Verify + Reward() []*Reward + Transactions() []*transaction.Transaction + ToProtoMessage() proto.Message + FromProtoMessage(message proto.Message) error +} + +type IBlock interface { + IHeader + Header() IHeader + Body() IBody + Transaction(hash types.Hash) *transaction.Transaction + Transactions() []*transaction.Transaction + Number64() *uint256.Int + Difficulty() *uint256.Int + Time() uint64 + GasLimit() uint64 + GasUsed() uint64 + Nonce() uint64 + Coinbase() types.Address + ParentHash() types.Hash + TxHash() types.Hash + WithSeal(header IHeader) *Block + //ToProtoMessage() proto.Message + //FromProtoMessage(message proto.Message) error +} + +type Blocks []IBlock diff --git a/common/block/log.go b/common/block/log.go new file mode 100644 index 0000000..96444a3 --- /dev/null +++ b/common/block/log.go @@ -0,0 +1,118 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "fmt" + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/types" + + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" +) + +type Log struct { + // Consensus fields: + // address of the contract that generated the event + Address types.Address `json:"address" gencodec:"required"` + // list of topics provided by the contract. + Topics []types.Hash `json:"topics" gencodec:"required"` + // supplied by the contract, usually ABI-encoded + Data []byte `json:"data" gencodec:"required"` + + // Derived fields. These fields are filled in by the node + // but not secured by consensus. + // block in which the transaction was included + BlockNumber *uint256.Int `json:"blockNumber"` + // hash of the transaction + TxHash types.Hash `json:"transactionHash" gencodec:"required"` + // index of the transaction in the block + TxIndex uint `json:"transactionIndex" gencodec:"required"` + // hash of the block in which the transaction was included + BlockHash types.Hash `json:"blockHash"` + // index of the log in the receipt + Index uint `json:"logIndex" gencodec:"required"` + + // The Removed field is true if this log was reverted due to a chain reorganisation. + // You must pay attention to this field if you receive logs through a filter query. + Removed bool `json:"removed"` +} + +func (l *Log) ToProtoMessage() proto.Message { + return &types_pb.Log{ + Address: utils.ConvertAddressToH160(l.Address), + Topics: utils.ConvertHashesToH256(l.Topics), + Data: l.Data, + BlockNumber: utils.ConvertUint256IntToH256(l.BlockNumber), + TxHash: utils.ConvertHashToH256(l.TxHash), + TxIndex: uint64(l.TxIndex), + BlockHash: utils.ConvertHashToH256(l.BlockHash), + Index: uint64(l.Index), + Removed: l.Removed, + } +} + +func (l *Log) FromProtoMessage(message proto.Message) error { + var ( + pLog *types_pb.Log + ok bool + ) + + if pLog, ok = message.(*types_pb.Log); !ok { + return fmt.Errorf("type conversion failure") + } + + l.Address = utils.ConvertH160toAddress(pLog.Address) + l.Topics = utils.H256sToHashes(pLog.Topics) + l.Data = pLog.Data + l.BlockNumber = utils.ConvertH256ToUint256Int(pLog.BlockNumber) + l.TxHash = utils.ConvertH256ToHash(pLog.TxHash) + l.TxIndex = uint(pLog.TxIndex) + l.BlockHash = utils.ConvertH256ToHash(pLog.BlockHash) + l.Index = uint(pLog.Index) + l.Removed = pLog.Removed + + return nil +} + +type Logs []*Log + +func (l *Logs) Marshal() ([]byte, error) { + pb := new(types_pb.Logs) + for _, log := range *l { + pb.Logs = append(pb.Logs, log.ToProtoMessage().(*types_pb.Log)) + } + + return proto.Marshal(pb) +} + +func (l *Logs) Unmarshal(data []byte) error { + pb := new(types_pb.Logs) + if err := proto.Unmarshal(data, pb); nil != err { + return err + } + + body := make([]*Log, len(pb.Logs)) + for i, p := range pb.Logs { + if err := body[i].FromProtoMessage(p); nil != err { + return err + } + } + *l = body + return nil +} diff --git a/common/block/receipt.go b/common/block/receipt.go new file mode 100644 index 0000000..7d03ac5 --- /dev/null +++ b/common/block/receipt.go @@ -0,0 +1,214 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package block + +import ( + "bytes" + "fmt" + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/avm/rlp" + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" +) + +const ( + // ReceiptStatusFailed is the status code of a transaction if execution failed. + ReceiptStatusFailed = uint64(0) + + // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. + ReceiptStatusSuccessful = uint64(1) +) + +type Receipts []*Receipt + +func (rs *Receipts) Marshal() ([]byte, error) { + pb := rs.ToProtoMessage() + return proto.Marshal(pb) +} + +func (rs *Receipts) Unmarshal(data []byte) error { + pb := new(types_pb.Receipts) + if err := proto.Unmarshal(data, pb); nil != err { + return err + } + + return rs.FromProtoMessage(pb) +} + +// Len returns the number of receipts in this list. +func (rs Receipts) Len() int { return len(rs) } + +// EncodeIndex encodes the i'th receipt to w. +func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { + r := rs[i] + + logs := make([]*storedLog, len(r.Logs)) + + for k, log := range r.Logs { + logs[k] = &storedLog{ + Address: log.Address, + Topics: log.Topics, + Data: log.Data, + } + } + data := &storedReceipt{r.Status, r.CumulativeGasUsed, logs} + + rlp.Encode(w, data) + //byte, _ := json.Marshal(data) + //w.Write(byte) +} + +func (rs *Receipts) FromProtoMessage(receipts *types_pb.Receipts) error { + for _, receipt := range receipts.Receipts { + var rec Receipt + err := rec.fromProtoMessage(receipt) + if err == nil { + *rs = append(*rs, &rec) + } + } + return nil +} + +func (rs *Receipts) ToProtoMessage() proto.Message { + var receipts []*types_pb.Receipt + for _, receipt := range *rs { + pReceipt := receipt.toProtoMessage() + receipts = append(receipts, pReceipt.(*types_pb.Receipt)) + } + return &types_pb.Receipts{ + Receipts: receipts, + } +} + +type Receipt struct { + // Consensus fields: These fields are defined by the Yellow Paper + Type uint8 `json:"type,omitempty"` + PostState []byte `json:"root"` + Status uint64 `json:"status"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Logs []*Log `json:"logs" gencodec:"required"` + + // Implementation fields: These fields are added by geth when processing a transaction. + // They are stored in the chain database. + TxHash types.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress types.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + + // Inclusion information: These fields provide information about the inclusion of the + // transaction corresponding to this receipt. + BlockHash types.Hash `json:"blockHash,omitempty"` + BlockNumber *uint256.Int `json:"blockNumber,omitempty"` + TransactionIndex uint `json:"transactionIndex"` +} + +func (r *Receipt) Marshal() ([]byte, error) { + bpBlock := r.toProtoMessage() + return proto.Marshal(bpBlock) +} + +func (r *Receipt) Unmarshal(data []byte) error { + var pReceipt types_pb.Receipt + if err := proto.Unmarshal(data, &pReceipt); err != nil { + return err + } + if err := r.fromProtoMessage(&pReceipt); err != nil { + return err + } + return nil +} + +func (r *Receipt) toProtoMessage() proto.Message { + //bloom, _ := r.Bloom.Marshal() + + var logs []*types_pb.Log + for _, log := range r.Logs { + logs = append(logs, log.ToProtoMessage().(*types_pb.Log)) + } + pb := &types_pb.Receipt{ + Type: uint32(r.Type), + PostState: r.PostState, + Status: r.Status, + CumulativeGasUsed: r.CumulativeGasUsed, + Logs: logs, + TxHash: utils.ConvertHashToH256(r.TxHash), + ContractAddress: utils.ConvertAddressToH160(r.ContractAddress), + GasUsed: r.GasUsed, + BlockHash: utils.ConvertHashToH256(r.BlockHash), + BlockNumber: utils.ConvertUint256IntToH256(r.BlockNumber), + TransactionIndex: uint64(r.TransactionIndex), + Bloom: utils.ConvertBytesToH2048(r.Bloom[:]), + } + return pb +} + +func (r *Receipt) fromProtoMessage(message proto.Message) error { + var ( + pReceipt *types_pb.Receipt + ok bool + ) + + if pReceipt, ok = message.(*types_pb.Receipt); !ok { + return fmt.Errorf("type conversion failure") + } + + //bloom := new(types.Bloom) + //err := bloom.UnMarshalBloom(pReceipt.Bloom) + //if err != nil { + // return fmt.Errorf("type conversion failure bloom") + //} + + var logs []*Log + for _, logMessage := range pReceipt.Logs { + log := new(Log) + + if err := log.FromProtoMessage(logMessage); err != nil { + return fmt.Errorf("type conversion failure log %s", err) + } + logs = append(logs, log) + } + + r.Type = uint8(pReceipt.Type) + r.PostState = pReceipt.PostState + r.Status = pReceipt.Status + r.CumulativeGasUsed = pReceipt.CumulativeGasUsed + r.Bloom = utils.ConvertH2048ToBloom(pReceipt.Bloom) + r.Logs = logs + r.TxHash = utils.ConvertH256ToHash(pReceipt.TxHash) + r.ContractAddress = utils.ConvertH160toAddress(pReceipt.ContractAddress) + r.GasUsed = pReceipt.GasUsed + r.BlockHash = utils.ConvertH256ToHash(pReceipt.BlockHash) + r.BlockNumber = utils.ConvertH256ToUint256Int(pReceipt.BlockNumber) + r.TransactionIndex = uint(pReceipt.TransactionIndex) + + return nil +} + +// storedReceipt is the consensus encoding of a receipt. +type storedReceipt struct { + PostStateOrStatus uint64 + CumulativeGasUsed uint64 + Logs []*storedLog +} + +type storedLog struct { + Address types.Address + Topics []types.Hash + Data []byte +} diff --git a/common/blockchain.go b/common/blockchain.go new file mode 100644 index 0000000..a22c040 --- /dev/null +++ b/common/blockchain.go @@ -0,0 +1,79 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/consensus" + "github.com/astranetworld/ast/modules/state" + "github.com/astranetworld/ast/params" + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/libp2p/go-libp2p/core/peer" +) + +type IHeaderChain interface { + GetHeaderByNumber(number *uint256.Int) block.IHeader + GetHeaderByHash(h types.Hash) (block.IHeader, error) + InsertHeader(headers []block.IHeader) (int, error) + GetBlockByHash(h types.Hash) (block.IBlock, error) + GetBlockByNumber(number *uint256.Int) (block.IBlock, error) +} + +type IBlockChain interface { + IHeaderChain + Config() *params.ChainConfig + CurrentBlock() block.IBlock + Blocks() []block.IBlock + Start() error + GenesisBlock() block.IBlock + NewBlockHandler(payload []byte, peer peer.ID) error + InsertChain(blocks []block.IBlock) (int, error) + InsertBlock(blocks []block.IBlock, isSync bool) (int, error) + SetEngine(engine consensus.Engine) + GetBlocksFromHash(hash types.Hash, n int) (blocks []block.IBlock) + SealedBlock(b block.IBlock) error + Engine() consensus.Engine + GetReceipts(blockHash types.Hash) (block.Receipts, error) + GetLogs(blockHash types.Hash) ([][]*block.Log, error) + SetHead(head uint64) error + AddFutureBlock(block block.IBlock) error + + GetHeader(types.Hash, *uint256.Int) block.IHeader + // alias for GetBlocksFromHash? + GetBlock(hash types.Hash, number uint64) block.IBlock + StateAt(tx kv.Tx, blockNr uint64) *state.IntraBlockState + + GetTd(hash types.Hash, number *uint256.Int) *uint256.Int + HasBlock(hash types.Hash, number uint64) bool + + DB() kv.RwDB + Quit() <-chan struct{} + + Close() error + + WriteBlockWithState(block block.IBlock, receipts []*block.Receipt, ibs *state.IntraBlockState, nopay map[types.Address]*uint256.Int) error + + GetDepositInfo(address types.Address) (*uint256.Int, *uint256.Int) + GetAccountRewardUnpaid(account types.Address) (*uint256.Int, error) +} + +type IMiner interface { + Start() + PendingBlockAndReceipts() (block.IBlock, block.Receipts) +} diff --git a/common/crypto/blake2b/blake2b.go b/common/crypto/blake2b/blake2b.go new file mode 100644 index 0000000..8e81a55 --- /dev/null +++ b/common/crypto/blake2b/blake2b.go @@ -0,0 +1,308 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693 +// and the extendable output function (XOF) BLAKE2Xb. +// +// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf +// and for BLAKE2Xb see https://blake2.net/blake2x.pdf +// +// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512). +// If you need a secret-key MAC (message authentication code), use the New512 +// function with a non-nil key. +// +// BLAKE2X is a construction to compute hash values larger than 64 bytes. It +// can produce hash values between 0 and 4 GiB. +package blake2b + +import ( + "encoding/binary" + "errors" + "hash" +) + +const ( + // The blocksize of BLAKE2b in bytes. + BlockSize = 128 + // The hash size of BLAKE2b-512 in bytes. + Size = 64 + // The hash size of BLAKE2b-384 in bytes. + Size384 = 48 + // The hash size of BLAKE2b-256 in bytes. + Size256 = 32 +) + +var ( + useAVX2 bool + useAVX bool + useSSE4 bool +) + +var ( + errKeySize = errors.New("blake2b: invalid key size") + errHashSize = errors.New("blake2b: invalid hash size") +) + +var iv = [8]uint64{ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +} + +// Sum512 returns the BLAKE2b-512 checksum of the data. +func Sum512(data []byte) [Size]byte { + var sum [Size]byte + checkSum(&sum, Size, data) + return sum +} + +// Sum384 returns the BLAKE2b-384 checksum of the data. +func Sum384(data []byte) [Size384]byte { + var sum [Size]byte + var sum384 [Size384]byte + checkSum(&sum, Size384, data) + copy(sum384[:], sum[:Size384]) + return sum384 +} + +// Sum256 returns the BLAKE2b-256 checksum of the data. +func Sum256(data []byte) [Size256]byte { + var sum [Size]byte + var sum256 [Size256]byte + checkSum(&sum, Size256, data) + copy(sum256[:], sum[:Size256]) + return sum256 +} + +// New512 returns a new hash.Hash computing the BLAKE2b-512 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New512(key []byte) (hash.Hash, error) { return newDigest(Size, key) } + +// New384 returns a new hash.Hash computing the BLAKE2b-384 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) } + +// New256 returns a new hash.Hash computing the BLAKE2b-256 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } + +// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length. +// A non-nil key turns the hash into a MAC. The key must be between zero and 64 bytes long. +// The hash size can be a value between 1 and 64 but it is highly recommended to use +// values equal or greater than: +// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long). +// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long). +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. +func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } + +// F is a compression function for BLAKE2b. It takes as an argument the state +// vector `h`, message block vector `m`, offset counter `t`, final block indicator +// flag `f`, and number of rounds `rounds`. The state vector provided as the first +// parameter is modified by the function. +func F(h *[8]uint64, m [16]uint64, c [2]uint64, final bool, rounds uint32) { + var flag uint64 + if final { + flag = 0xFFFFFFFFFFFFFFFF + } + f(h, &m, c[0], c[1], flag, uint64(rounds)) +} + +func newDigest(hashSize int, key []byte) (*digest, error) { + if hashSize < 1 || hashSize > Size { + return nil, errHashSize + } + if len(key) > Size { + return nil, errKeySize + } + d := &digest{ + size: hashSize, + keyLen: len(key), + } + copy(d.key[:], key) + d.Reset() + return d, nil +} + +func checkSum(sum *[Size]byte, hashSize int, data []byte) { + h := iv + h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) + var c [2]uint64 + + if length := len(data); length > BlockSize { + n := length &^ (BlockSize - 1) + if length == n { + n -= BlockSize + } + hashBlocks(&h, &c, 0, data[:n]) + data = data[n:] + } + + var block [BlockSize]byte + offset := copy(block[:], data) + remaining := uint64(BlockSize - offset) + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h[:(hashSize+7)/8] { + binary.LittleEndian.PutUint64(sum[8*i:], v) + } +} + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + var m [16]uint64 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + for j := range m { + m[j] = binary.LittleEndian.Uint64(blocks[i:]) + i += 8 + } + f(h, &m, c0, c1, flag, 12) + } + c[0], c[1] = c0, c1 +} + +type digest struct { + h [8]uint64 + c [2]uint64 + size int + block [BlockSize]byte + offset int + + key [BlockSize]byte + keyLen int +} + +const ( + magic = "b2b" + marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2b: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint64(b, d.h[i]) + } + b = appendUint64(b, d.c[0]) + b = appendUint64(b, d.c[1]) + // Maximum value for size is 64 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2b: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2b: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint64(b) + } + b, d.c[0] = consumeUint64(b) + b, d.c[1] = consumeUint64(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Size() int { return d.size } + +func (d *digest) Reset() { + d.h = iv + d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) + d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { + d.block = d.key + d.offset = BlockSize + } +} + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + + if d.offset > 0 { + remaining := BlockSize - d.offset + if n <= remaining { + d.offset += copy(d.block[d.offset:], p) + return + } + copy(d.block[d.offset:], p[:remaining]) + hashBlocks(&d.h, &d.c, 0, d.block[:]) + d.offset = 0 + p = p[remaining:] + } + + if length := len(p); length > BlockSize { + nn := length &^ (BlockSize - 1) + if length == nn { + nn -= BlockSize + } + hashBlocks(&d.h, &d.c, 0, p[:nn]) + p = p[nn:] + } + + if len(p) > 0 { + d.offset += copy(d.block[:], p) + } + + return +} + +func (d *digest) Sum(sum []byte) []byte { + var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) +} + +func (d *digest) finalize(hash *[Size]byte) { + var block [BlockSize]byte + copy(block[:], d.block[:d.offset]) + remaining := uint64(BlockSize - d.offset) + + c := d.c + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + h := d.h + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h { + binary.LittleEndian.PutUint64(hash[8*i:], v) + } +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := binary.BigEndian.Uint64(b) + return b[8:], x +} diff --git a/common/crypto/blake2b/blake2bAVX2_amd64.go b/common/crypto/blake2b/blake2bAVX2_amd64.go new file mode 100644 index 0000000..bfeee2b --- /dev/null +++ b/common/crypto/blake2b/blake2bAVX2_amd64.go @@ -0,0 +1,37 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.7 && amd64 && !gccgo && !appengine + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useAVX2 = cpu.X86.HasAVX2 + useAVX = cpu.X86.HasAVX + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func fAVX2(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +//go:noescape +func fAVX(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +//go:noescape +func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + switch { + case useAVX2: + fAVX2(h, m, c0, c1, flag, rounds) + case useAVX: + fAVX(h, m, c0, c1, flag, rounds) + case useSSE4: + fSSE4(h, m, c0, c1, flag, rounds) + default: + fGeneric(h, m, c0, c1, flag, rounds) + } +} diff --git a/common/crypto/blake2b/blake2bAVX2_amd64.s b/common/crypto/blake2b/blake2bAVX2_amd64.s new file mode 100644 index 0000000..4998af3 --- /dev/null +++ b/common/crypto/blake2b/blake2bAVX2_amd64.s @@ -0,0 +1,717 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7,amd64,!gccgo,!appengine + +#include "textflag.h" + +DATA ·AVX2_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX2_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +DATA ·AVX2_iv0<>+0x10(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX2_iv0<>+0x18(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX2_iv0<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_iv1<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX2_iv1<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +DATA ·AVX2_iv1<>+0x10(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX2_iv1<>+0x18(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX2_iv1<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +DATA ·AVX2_c40<>+0x10(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x18(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX2_c40<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +DATA ·AVX2_c48<>+0x10(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x18(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX2_c48<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·AVX_iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX_iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX_iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX_iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·AVX_iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX_iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX_iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX_c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX_c48<>(SB), (NOPTR+RODATA), $16 + +#define VPERMQ_0x39_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x39 +#define VPERMQ_0x93_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x93 +#define VPERMQ_0x4E_Y2_Y2 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xd2; BYTE $0x4e +#define VPERMQ_0x93_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x93 +#define VPERMQ_0x39_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x39 + +#define ROUND_AVX2(m0, m1, m2, m3, t, c40, c48) \ + VPADDQ m0, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m1, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y1_Y1; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y3_Y3; \ + VPADDQ m2, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m3, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y3_Y3; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y1_Y1 + +#define VMOVQ_SI_X11_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x1E +#define VMOVQ_SI_X12_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x26 +#define VMOVQ_SI_X13_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x2E +#define VMOVQ_SI_X14_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x36 +#define VMOVQ_SI_X15_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x3E + +#define VMOVQ_SI_X11(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x5E; BYTE $n +#define VMOVQ_SI_X12(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x66; BYTE $n +#define VMOVQ_SI_X13(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x6E; BYTE $n +#define VMOVQ_SI_X14(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x76; BYTE $n +#define VMOVQ_SI_X15(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x7E; BYTE $n + +#define VPINSRQ_1_SI_X11_0 BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x1E; BYTE $0x01 +#define VPINSRQ_1_SI_X12_0 BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x26; BYTE $0x01 +#define VPINSRQ_1_SI_X13_0 BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x2E; BYTE $0x01 +#define VPINSRQ_1_SI_X14_0 BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x36; BYTE $0x01 +#define VPINSRQ_1_SI_X15_0 BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x3E; BYTE $0x01 + +#define VPINSRQ_1_SI_X11(n) BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x5E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X12(n) BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x66; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X13(n) BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x6E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X14(n) BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x76; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X15(n) BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x7E; BYTE $n; BYTE $0x01 + +#define VMOVQ_R8_X15 BYTE $0xC4; BYTE $0x41; BYTE $0xF9; BYTE $0x6E; BYTE $0xF8 +#define VPINSRQ_1_R9_X15 BYTE $0xC4; BYTE $0x43; BYTE $0x81; BYTE $0x22; BYTE $0xF9; BYTE $0x01 + +// load msg: Y12 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y12(i0, i1, i2, i3) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y12, Y12 + +// load msg: Y13 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y13(i0, i1, i2, i3) \ + VMOVQ_SI_X13(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X13(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y13, Y13 + +// load msg: Y14 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y14(i0, i1, i2, i3) \ + VMOVQ_SI_X14(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X14(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y14, Y14 + +// load msg: Y15 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y15(i0, i1, i2, i3) \ + VMOVQ_SI_X15(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X15(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X11(6*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(1, 3, 5, 7); \ + LOAD_MSG_AVX2_Y14(8, 10, 12, 14); \ + LOAD_MSG_AVX2_Y15(9, 11, 13, 15) + +#define LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() \ + LOAD_MSG_AVX2_Y12(14, 4, 9, 13); \ + LOAD_MSG_AVX2_Y13(10, 8, 15, 6); \ + VMOVQ_SI_X11(11*8); \ + VPSHUFD $0x4E, 0*8(SI), X14; \ + VPINSRQ_1_SI_X11(5*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(12, 2, 7, 3) + +#define LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() \ + VMOVQ_SI_X11(5*8); \ + VMOVDQU 11*8(SI), X12; \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + VMOVQ_SI_X13(8*8); \ + VMOVQ_SI_X11(2*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X11(13*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(10, 3, 7, 9); \ + LOAD_MSG_AVX2_Y15(14, 6, 1, 4) + +#define LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() \ + LOAD_MSG_AVX2_Y12(7, 3, 13, 11); \ + LOAD_MSG_AVX2_Y13(9, 1, 12, 14); \ + LOAD_MSG_AVX2_Y14(2, 5, 4, 15); \ + VMOVQ_SI_X15(6*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X15(10*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() \ + LOAD_MSG_AVX2_Y12(9, 5, 2, 10); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X13(7*8); \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(14, 11, 6, 3); \ + LOAD_MSG_AVX2_Y15(1, 12, 8, 13) + +#define LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(12, 10, 11, 3); \ + LOAD_MSG_AVX2_Y14(4, 7, 15, 1); \ + LOAD_MSG_AVX2_Y15(13, 5, 14, 9) + +#define LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() \ + LOAD_MSG_AVX2_Y12(12, 1, 14, 4); \ + LOAD_MSG_AVX2_Y13(5, 15, 13, 10); \ + VMOVQ_SI_X14_0; \ + VPSHUFD $0x4E, 8*8(SI), X11; \ + VPINSRQ_1_SI_X14(6*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(7, 3, 2, 11) + +#define LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() \ + LOAD_MSG_AVX2_Y12(13, 7, 12, 3); \ + LOAD_MSG_AVX2_Y13(11, 14, 1, 9); \ + LOAD_MSG_AVX2_Y14(5, 15, 8, 2); \ + VMOVQ_SI_X15_0; \ + VMOVQ_SI_X11(6*8); \ + VPINSRQ_1_SI_X15(4*8); \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() \ + VMOVQ_SI_X12(6*8); \ + VMOVQ_SI_X11(11*8); \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(15, 9, 3, 8); \ + VMOVQ_SI_X11(1*8); \ + VMOVDQU 12*8(SI), X14; \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + VMOVQ_SI_X15(2*8); \ + VMOVDQU 4*8(SI), X11; \ + VPINSRQ_1_SI_X15(7*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() \ + LOAD_MSG_AVX2_Y12(10, 8, 7, 1); \ + VMOVQ_SI_X13(2*8); \ + VPSHUFD $0x4E, 5*8(SI), X11; \ + VPINSRQ_1_SI_X13(4*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(15, 9, 3, 13); \ + VMOVQ_SI_X15(11*8); \ + VMOVQ_SI_X11(12*8); \ + VPINSRQ_1_SI_X15(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y15, Y15 + +// func fAVX2(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fAVX2(SB), 4, $64-48 // frame size = 32 + 32 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, DX + MOVQ SP, R10 + ADDQ $31, R10 + ANDQ $~31, R10 + MOVQ R10, SP + + MOVQ CX, 16(SP) + XORQ CX, CX + MOVQ CX, 24(SP) + + VMOVDQU ·AVX2_c40<>(SB), Y4 + VMOVDQU ·AVX2_c48<>(SB), Y5 + + VMOVDQU 0(AX), Y8 + VMOVDQU 32(AX), Y9 + VMOVDQU ·AVX2_iv0<>(SB), Y6 + VMOVDQU ·AVX2_iv1<>(SB), Y7 + + MOVQ R8, 0(SP) + MOVQ R9, 8(SP) + + VMOVDQA Y8, Y0 + VMOVDQA Y9, Y1 + VMOVDQA Y6, Y2 + VPXOR 0(SP), Y7, Y3 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + JMP loop + +done: + VPXOR Y0, Y8, Y8 + VPXOR Y1, Y9, Y9 + VPXOR Y2, Y8, Y8 + VPXOR Y3, Y9, Y9 + + VMOVDQU Y8, 0(AX) + VMOVDQU Y9, 32(AX) + VZEROUPPER + + MOVQ DX, SP + RET + +#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA +#define VPUNPCKLQDQ_X3_X3_X15 BYTE $0xC5; BYTE $0x61; BYTE $0x6C; BYTE $0xFB +#define VPUNPCKLQDQ_X7_X7_X15 BYTE $0xC5; BYTE $0x41; BYTE $0x6C; BYTE $0xFF +#define VPUNPCKLQDQ_X13_X13_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x11; BYTE $0x6C; BYTE $0xFD +#define VPUNPCKLQDQ_X14_X14_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x09; BYTE $0x6C; BYTE $0xFE + +#define VPUNPCKHQDQ_X15_X2_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x69; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X3_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X6_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x49; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X7_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xFF +#define VPUNPCKHQDQ_X15_X3_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X7_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X13_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X13_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xFF + +#define SHUFFLE_AVX() \ + VMOVDQA X6, X13; \ + VMOVDQA X2, X14; \ + VMOVDQA X4, X6; \ + VPUNPCKLQDQ_X13_X13_X15; \ + VMOVDQA X5, X4; \ + VMOVDQA X6, X5; \ + VPUNPCKHQDQ_X15_X7_X6; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X13_X7; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VPUNPCKHQDQ_X15_X2_X2; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X3_X3; \ + +#define SHUFFLE_AVX_INV() \ + VMOVDQA X2, X13; \ + VMOVDQA X4, X14; \ + VPUNPCKLQDQ_X2_X2_X15; \ + VMOVDQA X5, X4; \ + VPUNPCKHQDQ_X15_X3_X2; \ + VMOVDQA X14, X5; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VMOVDQA X6, X14; \ + VPUNPCKHQDQ_X15_X13_X3; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X6_X6; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X7_X7; \ + +#define HALF_ROUND_AVX(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + VPADDQ m0, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m1, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFD $-79, v6, v6; \ + VPSHUFD $-79, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPSHUFB c40, v2, v2; \ + VPSHUFB c40, v3, v3; \ + VPADDQ m2, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m3, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFB c48, v6, v6; \ + VPSHUFB c48, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPADDQ v2, v2, t0; \ + VPSRLQ $63, v2, v2; \ + VPXOR t0, v2, v2; \ + VPADDQ v3, v3, t0; \ + VPSRLQ $63, v3, v3; \ + VPXOR t0, v3, v3 + +// load msg: X12 = (i0, i1), X13 = (i2, i3), X14 = (i4, i5), X15 = (i6, i7) +// i0, i1, i2, i3, i4, i5, i6, i7 must not be 0 +#define LOAD_MSG_AVX(i0, i1, i2, i3, i4, i5, i6, i7) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X13(i2*8); \ + VMOVQ_SI_X14(i4*8); \ + VMOVQ_SI_X15(i6*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X13(i3*8); \ + VPINSRQ_1_SI_X14(i5*8); \ + VPINSRQ_1_SI_X15(i7*8) + +// load msg: X12 = (0, 2), X13 = (4, 6), X14 = (1, 3), X15 = (5, 7) +#define LOAD_MSG_AVX_0_2_4_6_1_3_5_7() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(1*8); \ + VMOVQ_SI_X15(5*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X13(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(7*8) + +// load msg: X12 = (1, 0), X13 = (11, 5), X14 = (12, 2), X15 = (7, 3) +#define LOAD_MSG_AVX_1_0_11_5_12_2_7_3() \ + VPSHUFD $0x4E, 0*8(SI), X12; \ + VMOVQ_SI_X13(11*8); \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(7*8); \ + VPINSRQ_1_SI_X13(5*8); \ + VPINSRQ_1_SI_X14(2*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (11, 12), X13 = (5, 15), X14 = (8, 0), X15 = (2, 13) +#define LOAD_MSG_AVX_11_12_5_15_8_0_2_13() \ + VMOVDQU 11*8(SI), X12; \ + VMOVQ_SI_X13(5*8); \ + VMOVQ_SI_X14(8*8); \ + VMOVQ_SI_X15(2*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14_0; \ + VPINSRQ_1_SI_X15(13*8) + +// load msg: X12 = (2, 5), X13 = (4, 15), X14 = (6, 10), X15 = (0, 8) +#define LOAD_MSG_AVX_2_5_4_15_6_10_0_8() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(6*8); \ + VMOVQ_SI_X15_0; \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (9, 5), X13 = (2, 10), X14 = (0, 7), X15 = (4, 15) +#define LOAD_MSG_AVX_9_5_2_10_0_7_4_15() \ + VMOVQ_SI_X12(9*8); \ + VMOVQ_SI_X13(2*8); \ + VMOVQ_SI_X14_0; \ + VMOVQ_SI_X15(4*8); \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VPINSRQ_1_SI_X15(15*8) + +// load msg: X12 = (2, 6), X13 = (0, 8), X14 = (12, 10), X15 = (11, 3) +#define LOAD_MSG_AVX_2_6_0_8_12_10_11_3() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(11*8); \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X13(8*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (0, 6), X13 = (9, 8), X14 = (7, 3), X15 = (2, 11) +#define LOAD_MSG_AVX_0_6_9_8_7_3_2_11() \ + MOVQ 0*8(SI), X12; \ + VPSHUFD $0x4E, 8*8(SI), X13; \ + MOVQ 7*8(SI), X14; \ + MOVQ 2*8(SI), X15; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(11*8) + +// load msg: X12 = (6, 14), X13 = (11, 0), X14 = (15, 9), X15 = (3, 8) +#define LOAD_MSG_AVX_6_14_11_0_15_9_3_8() \ + MOVQ 6*8(SI), X12; \ + MOVQ 11*8(SI), X13; \ + MOVQ 15*8(SI), X14; \ + MOVQ 3*8(SI), X15; \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X14(9*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (5, 15), X13 = (8, 2), X14 = (0, 4), X15 = (6, 10) +#define LOAD_MSG_AVX_5_15_8_2_0_4_6_10() \ + MOVQ 5*8(SI), X12; \ + MOVQ 8*8(SI), X13; \ + MOVQ 0*8(SI), X14; \ + MOVQ 6*8(SI), X15; \ + VPINSRQ_1_SI_X12(15*8); \ + VPINSRQ_1_SI_X13(2*8); \ + VPINSRQ_1_SI_X14(4*8); \ + VPINSRQ_1_SI_X15(10*8) + +// load msg: X12 = (12, 13), X13 = (1, 10), X14 = (2, 7), X15 = (4, 5) +#define LOAD_MSG_AVX_12_13_1_10_2_7_4_5() \ + VMOVDQU 12*8(SI), X12; \ + MOVQ 1*8(SI), X13; \ + MOVQ 2*8(SI), X14; \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VMOVDQU 4*8(SI), X15 + +// load msg: X12 = (15, 9), X13 = (3, 13), X14 = (11, 14), X15 = (12, 0) +#define LOAD_MSG_AVX_15_9_3_13_11_14_12_0() \ + MOVQ 15*8(SI), X12; \ + MOVQ 3*8(SI), X13; \ + MOVQ 11*8(SI), X14; \ + MOVQ 12*8(SI), X15; \ + VPINSRQ_1_SI_X12(9*8); \ + VPINSRQ_1_SI_X13(13*8); \ + VPINSRQ_1_SI_X14(14*8); \ + VPINSRQ_1_SI_X15_0 + +// func fAVX(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fAVX(SB), 4, $24-48 // frame size = 8 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, BP + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + MOVQ R10, SP + + VMOVDQU ·AVX_c40<>(SB), X0 + VMOVDQU ·AVX_c48<>(SB), X1 + VMOVDQA X0, X8 + VMOVDQA X1, X9 + + VMOVDQU ·AVX_iv3<>(SB), X0 + VMOVDQA X0, 0(SP) + XORQ CX, 0(SP) // 0(SP) = ·AVX_iv3 ^ (CX || 0) + + VMOVDQU 0(AX), X10 + VMOVDQU 16(AX), X11 + VMOVDQU 32(AX), X2 + VMOVDQU 48(AX), X3 + + VMOVQ_R8_X15 + VPINSRQ_1_R9_X15 + + VMOVDQA X10, X0 + VMOVDQA X11, X1 + VMOVDQU ·AVX_iv0<>(SB), X4 + VMOVDQU ·AVX_iv1<>(SB), X5 + VMOVDQU ·AVX_iv2<>(SB), X6 + + VPXOR X15, X6, X6 + VMOVDQA 0(SP), X7 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_0_2_4_6_1_3_5_7() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_1_0_11_5_12_2_7_3() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_11_12_5_15_8_0_2_13() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_2_5_4_15_6_10_0_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_9_5_2_10_0_7_4_15() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_2_6_0_8_12_10_11_3() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_0_6_9_8_7_3_2_11() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_5_15_8_2_0_4_6_10() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_6_14_11_0_15_9_3_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_12_13_1_10_2_7_4_5() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_15_9_3_13_11_14_12_0() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + JMP loop + +done: + VMOVDQU 32(AX), X14 + VMOVDQU 48(AX), X15 + VPXOR X0, X10, X10 + VPXOR X1, X11, X11 + VPXOR X2, X14, X14 + VPXOR X3, X15, X15 + VPXOR X4, X10, X10 + VPXOR X5, X11, X11 + VPXOR X6, X14, X2 + VPXOR X7, X15, X3 + VMOVDQU X2, 32(AX) + VMOVDQU X3, 48(AX) + + VMOVDQU X10, 0(AX) + VMOVDQU X11, 16(AX) + VZEROUPPER + + MOVQ BP, SP + RET diff --git a/common/crypto/blake2b/blake2b_amd64.go b/common/crypto/blake2b/blake2b_amd64.go new file mode 100644 index 0000000..7f933ec --- /dev/null +++ b/common/crypto/blake2b/blake2b_amd64.go @@ -0,0 +1,24 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.7 && amd64 && !gccgo && !appengine + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + if useSSE4 { + fSSE4(h, m, c0, c1, flag, rounds) + } else { + fGeneric(h, m, c0, c1, flag, rounds) + } +} diff --git a/common/crypto/blake2b/blake2b_amd64.s b/common/crypto/blake2b/blake2b_amd64.s new file mode 100644 index 0000000..ce4b56d --- /dev/null +++ b/common/crypto/blake2b/blake2b_amd64.s @@ -0,0 +1,253 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!gccgo,!appengine + +#include "textflag.h" + +DATA ·iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 + +#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v6, t1; \ + PUNPCKLQDQ v6, t2; \ + PUNPCKHQDQ v7, v6; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ v7, t2; \ + MOVO t1, v7; \ + MOVO v2, t1; \ + PUNPCKHQDQ t2, v7; \ + PUNPCKLQDQ v3, t2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v3 + +#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v2, t1; \ + PUNPCKLQDQ v2, t2; \ + PUNPCKHQDQ v3, v2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ v3, t2; \ + MOVO t1, v3; \ + MOVO v6, t1; \ + PUNPCKHQDQ t2, v3; \ + PUNPCKLQDQ v7, t2; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v7 + +#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + PADDQ m0, v0; \ + PADDQ m1, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFD $0xB1, v6, v6; \ + PSHUFD $0xB1, v7, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + PSHUFB c40, v2; \ + PSHUFB c40, v3; \ + PADDQ m2, v0; \ + PADDQ m3, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFB c48, v6; \ + PSHUFB c48, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + MOVOU v2, t0; \ + PADDQ v2, t0; \ + PSRLQ $63, v2; \ + PXOR t0, v2; \ + MOVOU v3, t0; \ + PADDQ v3, t0; \ + PSRLQ $63, v3; \ + PXOR t0, v3 + +#define LOAD_MSG(m0, m1, m2, m3, i0, i1, i2, i3, i4, i5, i6, i7) \ + MOVQ i0*8(SI), m0; \ + PINSRQ $1, i1*8(SI), m0; \ + MOVQ i2*8(SI), m1; \ + PINSRQ $1, i3*8(SI), m1; \ + MOVQ i4*8(SI), m2; \ + PINSRQ $1, i5*8(SI), m2; \ + MOVQ i6*8(SI), m3; \ + PINSRQ $1, i7*8(SI), m3 + +// func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fSSE4(SB), 4, $24-48 // frame size = 8 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, BP + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + MOVQ R10, SP + + MOVOU ·iv3<>(SB), X0 + MOVO X0, 0(SP) + XORQ CX, 0(SP) // 0(SP) = ·iv3 ^ (CX || 0) + + MOVOU ·c40<>(SB), X13 + MOVOU ·c48<>(SB), X14 + + MOVOU 0(AX), X12 + MOVOU 16(AX), X15 + + MOVQ R8, X8 + PINSRQ $1, R9, X8 + + MOVO X12, X0 + MOVO X15, X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU ·iv0<>(SB), X4 + MOVOU ·iv1<>(SB), X5 + MOVOU ·iv2<>(SB), X6 + + PXOR X8, X6 + MOVO 0(SP), X7 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 0, 2, 4, 6, 1, 3, 5, 7) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 8, 10, 12, 14, 9, 11, 13, 15) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 14, 4, 9, 13, 10, 8, 15, 6) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 1, 0, 11, 5, 12, 2, 7, 3) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 11, 12, 5, 15, 8, 0, 2, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 2, 5, 4, 15, 6, 10, 0, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 9, 5, 2, 10, 0, 7, 4, 15) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 2, 6, 0, 8, 12, 10, 11, 3) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 0, 6, 9, 8, 7, 3, 2, 11) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 5, 15, 8, 2, 0, 4, 6, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 6, 14, 11, 0, 15, 9, 3, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 12, 13, 1, 10, 2, 7, 4, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 15, 9, 3, 13, 11, 14, 12, 0) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + JMP loop + +done: + MOVOU 32(AX), X10 + MOVOU 48(AX), X11 + PXOR X0, X12 + PXOR X1, X15 + PXOR X2, X10 + PXOR X3, X11 + PXOR X4, X12 + PXOR X5, X15 + PXOR X6, X10 + PXOR X7, X11 + MOVOU X10, 32(AX) + MOVOU X11, 48(AX) + + MOVOU X12, 0(AX) + MOVOU X15, 16(AX) + + MOVQ BP, SP + RET diff --git a/common/crypto/blake2b/blake2b_f_fuzz.go b/common/crypto/blake2b/blake2b_f_fuzz.go new file mode 100644 index 0000000..5b7eb67 --- /dev/null +++ b/common/crypto/blake2b/blake2b_f_fuzz.go @@ -0,0 +1,57 @@ +//go:build gofuzz + +package blake2b + +import ( + "encoding/binary" +) + +func Fuzz(data []byte) int { + // Make sure the data confirms to the input model + if len(data) != 211 { + return 0 + } + // Parse everything and call all the implementations + var ( + rounds = binary.BigEndian.Uint16(data[0:2]) + + h [8]uint64 + m [16]uint64 + t [2]uint64 + f uint64 + ) + for i := 0; i < 8; i++ { + offset := 2 + i*8 + h[i] = binary.LittleEndian.Uint64(data[offset : offset+8]) + } + for i := 0; i < 16; i++ { + offset := 66 + i*8 + m[i] = binary.LittleEndian.Uint64(data[offset : offset+8]) + } + t[0] = binary.LittleEndian.Uint64(data[194:202]) + t[1] = binary.LittleEndian.Uint64(data[202:210]) + + if data[210]%2 == 1 { // Avoid spinning the fuzzer to hit 0/1 + f = 0xFFFFFFFFFFFFFFFF + } + // Run the blake2b compression on all instruction sets and cross reference + want := h + fGeneric(&want, &m, t[0], t[1], f, uint64(rounds)) + + have := h + fSSE4(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("SSE4 mismatches generic algo") + } + have = h + fAVX(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("AVX mismatches generic algo") + } + have = h + fAVX2(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("AVX2 mismatches generic algo") + } + return 1 +} diff --git a/common/crypto/blake2b/blake2b_f_test.go b/common/crypto/blake2b/blake2b_f_test.go new file mode 100644 index 0000000..4e07d13 --- /dev/null +++ b/common/crypto/blake2b/blake2b_f_test.go @@ -0,0 +1,59 @@ +package blake2b + +import ( + "fmt" + "reflect" + "testing" +) + +func TestF(t *testing.T) { + for i, test := range testVectorsF { + t.Run(fmt.Sprintf("test vector %v", i), func(t *testing.T) { + //toEthereumTestCase(test) + + h := test.hIn + F(&h, test.m, test.c, test.f, test.rounds) + + if !reflect.DeepEqual(test.hOut, h) { + t.Errorf("Unexpected result\nExpected: [%#x]\nActual: [%#x]\n", test.hOut, h) + } + }) + } +} + +type testVector struct { + hIn [8]uint64 + m [16]uint64 + c [2]uint64 + f bool + rounds uint32 + hOut [8]uint64 +} + +// https://tools.ietf.org/html/rfc7693#appendix-A +var testVectorsF = []testVector{ + { + hIn: [8]uint64{ + 0x6a09e667f2bdc948, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }, + m: [16]uint64{ + 0x0000000000636261, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, + }, + c: [2]uint64{3, 0}, + f: true, + rounds: 12, + hOut: [8]uint64{ + 0x0D4D1C983FA580BA, 0xE9F6129FB697276A, 0xB7C45A68142F214C, + 0xD1A2FFDB6FBB124B, 0x2D79AB2A39C5877D, 0x95CC3345DED552C2, + 0x5A92F1DBA88AD318, 0x239900D4ED8623B9, + }, + }, +} diff --git a/common/crypto/blake2b/blake2b_generic.go b/common/crypto/blake2b/blake2b_generic.go new file mode 100644 index 0000000..b506e30 --- /dev/null +++ b/common/crypto/blake2b/blake2b_generic.go @@ -0,0 +1,161 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "math/bits" +) + +// the precomputed values for BLAKE2b +// there are 10 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [10][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, +} + +func fGeneric(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7] + v12 ^= c0 + v13 ^= c1 + v14 ^= flag + + for i := 0; i < int(rounds); i++ { + s := &(precomputed[i%10]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -32) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -24) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -32) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -24) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -32) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -24) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -32) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -24) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -63) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -63) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -63) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -63) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -32) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -24) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -32) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -24) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -32) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -24) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -32) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -24) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -63) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -63) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -63) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -63) + } + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 +} diff --git a/common/crypto/blake2b/blake2b_ref.go b/common/crypto/blake2b/blake2b_ref.go new file mode 100644 index 0000000..6825441 --- /dev/null +++ b/common/crypto/blake2b/blake2b_ref.go @@ -0,0 +1,11 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || appengine || gccgo + +package blake2b + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + fGeneric(h, m, c0, c1, flag, rounds) +} diff --git a/common/crypto/blake2b/blake2b_test.go b/common/crypto/blake2b/blake2b_test.go new file mode 100644 index 0000000..14f2e3b --- /dev/null +++ b/common/crypto/blake2b/blake2b_test.go @@ -0,0 +1,868 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "bytes" + "encoding" + "encoding/hex" + "fmt" + "github.com/ledgerwatch/log/v3" + "hash" + "io" + "testing" +) + +func TestHashes(t *testing.T) { + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + + if useAVX2 { + t.Log("AVX2 version") + testHashes(t) + useAVX2 = false + } + if useAVX { + t.Log("AVX version") + testHashes(t) + useAVX = false + } + if useSSE4 { + t.Log("SSE4 version") + testHashes(t) + useSSE4 = false + } + t.Log("generic version") + testHashes(t) +} + +func TestHashes2X(t *testing.T) { + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + + if useAVX2 { + t.Log("AVX2 version") + testHashes2X(t) + useAVX2 = false + } + if useAVX { + t.Log("AVX version") + testHashes2X(t) + useAVX = false + } + if useSSE4 { + t.Log("SSE4 version") + testHashes2X(t) + useSSE4 = false + } + t.Log("generic version") + testHashes2X(t) +} + +func TestMarshal(t *testing.T) { + input := make([]byte, 255) + for i := range input { + input[i] = byte(i) + } + for _, size := range []int{Size, Size256, Size384, 12, 25, 63} { + for i := 0; i < 256; i++ { + h, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + h2, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + + h.Write(input[:i/2]) + halfstate, err := h.(encoding.BinaryMarshaler).MarshalBinary() + if err != nil { + t.Fatalf("size=%d, len(input)=%d: could not marshal: %v", size, i, err) + } + binUnmarshaler, ok := h2.(encoding.BinaryUnmarshaler) + if !ok { + log.Warn("Failed to type convert h2 to encoding.BinaryUnmarshaler") + } + err = binUnmarshaler.UnmarshalBinary(halfstate) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: could not unmarshal: %v", size, i, err) + } + + h.Write(input[i/2 : i]) + sum := h.Sum(nil) + h2.Write(input[i/2 : i]) + sum2 := h2.Sum(nil) + + if !bytes.Equal(sum, sum2) { + t.Fatalf("size=%d, len(input)=%d: results do not match; sum = %v, sum2 = %v", size, i, sum, sum2) + } + + h3, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + h3.Write(input[:i]) + sum3 := h3.Sum(nil) + if !bytes.Equal(sum, sum3) { + t.Fatalf("size=%d, len(input)=%d: sum = %v, want %v", size, i, sum, sum3) + } + } + } +} + +func testHashes(t *testing.T) { + key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f") + + input := make([]byte, 255) + for i := range input { + input[i] = byte(i) + } + + for i, expectedHex := range hashes { + h, err := New512(key) + if err != nil { + t.Fatalf("#%d: error from New512: %v", i, err) + } + + h.Write(input[:i]) + sum := h.Sum(nil) + + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex) + } + + h.Reset() + for j := 0; j < i; j++ { + h.Write(input[j : j+1]) + } + + sum = h.Sum(sum[:0]) + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex) + } + } +} + +func testHashes2X(t *testing.T) { + key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f") + + input := make([]byte, 256) + for i := range input { + input[i] = byte(i) + } + + for i, expectedHex := range hashes2X { + length := uint32(len(expectedHex) / 2) + sum := make([]byte, int(length)) + + h, err := NewXOF(length, key) + if err != nil { + t.Fatalf("#%d: error from NewXOF: %v", i, err) + } + + if _, err := h.Write(input); err != nil { + t.Fatalf("#%d (single write): error from Write: %v", i, err) + } + if _, err := h.Read(sum); err != nil { + t.Fatalf("#%d (single write): error from Read: %v", i, err) + } + if n, err := h.Read(sum); n != 0 || err != io.EOF { + t.Fatalf("#%d (single write): Read did not return (0, io.EOF) after exhaustion, got (%v, %v)", i, n, err) + } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex) + } + + h.Reset() + for j := 0; j < len(input); j++ { + h.Write(input[j : j+1]) + } + for j := 0; j < len(sum); j++ { + h = h.Clone() + if _, err := h.Read(sum[j : j+1]); err != nil { + t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err) + } + } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex) + } + } + + h, err := NewXOF(OutputLengthUnknown, key) + if err != nil { + t.Fatalf("#unknown length: error from NewXOF: %v", err) + } + if _, err := h.Write(input); err != nil { + t.Fatalf("#unknown length: error from Write: %v", err) + } + + var result [64]byte + if n, err := h.Read(result[:]); err != nil { + t.Fatalf("#unknown length: error from Read: %v", err) + } else if n != len(result) { + t.Fatalf("#unknown length: Read returned %d bytes, want %d", n, len(result)) + } + + const expected = "3dbba8516da76bf7330055c66ea36cf1005e92714262b24d9710f51d9e126406e1bcd6497059f9331f1091c3634b695428d475ed432f987040575520a1c29f5e" + if fmt.Sprintf("%x", result) != expected { + t.Fatalf("#unknown length: bad result %x, wanted %s", result, expected) + } +} + +func generateSequence(out []byte, seed uint32) { + a := 0xDEAD4BAD * seed // prime + b := uint32(1) + + for i := range out { // fill the buf + a, b = b, a+b + out[i] = byte(b >> 24) + } +} + +func computeMAC(msg []byte, hashSize int, key []byte) (sum []byte) { + var h hash.Hash + switch hashSize { + case Size: + h, _ = New512(key) + case Size384: + h, _ = New384(key) + case Size256: + h, _ = New256(key) + case 20: + h, _ = newDigest(20, key) + default: + panic("unexpected hashSize") + } + + h.Write(msg) + return h.Sum(sum) +} + +func computeHash(msg []byte, hashSize int) (sum []byte) { + switch hashSize { + case Size: + hash := Sum512(msg) + return hash[:] + case Size384: + hash := Sum384(msg) + return hash[:] + case Size256: + hash := Sum256(msg) + return hash[:] + case 20: + var hash [64]byte + checkSum(&hash, 20, msg) + return hash[:20] + default: + panic("unexpected hashSize") + } +} + +// Test function from RFC 7693. +func TestSelfTest(t *testing.T) { + hashLens := [4]int{20, 32, 48, 64} + msgLens := [6]int{0, 3, 128, 129, 255, 1024} + + msg := make([]byte, 1024) + key := make([]byte, 64) + + h, _ := New256(nil) + for _, hashSize := range hashLens { + for _, msgLength := range msgLens { + generateSequence(msg[:msgLength], uint32(msgLength)) // unkeyed hash + + md := computeHash(msg[:msgLength], hashSize) + h.Write(md) + + generateSequence(key[:], uint32(hashSize)) // keyed hash + md = computeMAC(msg[:msgLength], hashSize, key[:hashSize]) + h.Write(md) + } + } + + sum := h.Sum(nil) + expected := [32]byte{ + 0xc2, 0x3a, 0x78, 0x00, 0xd9, 0x81, 0x23, 0xbd, + 0x10, 0xf5, 0x06, 0xc6, 0x1e, 0x29, 0xda, 0x56, + 0x03, 0xd7, 0x63, 0xb8, 0xbb, 0xad, 0x2e, 0x73, + 0x7f, 0x5e, 0x76, 0x5a, 0x7b, 0xcc, 0xd4, 0x75, + } + if !bytes.Equal(sum, expected[:]) { + t.Fatalf("got %x, wanted %x", sum, expected) + } +} + +// Benchmarks + +func benchmarkSum(b *testing.B, size int, sse4, avx, avx2 bool) { + // Enable the correct set of instructions + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + + data := make([]byte, size) + b.SetBytes(int64(size)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Sum512(data) + } +} + +func benchmarkWrite(b *testing.B, size int, sse4, avx, avx2 bool) { + // Enable the correct set of instructions + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + + data := make([]byte, size) + h, _ := New512(nil) + b.SetBytes(int64(size)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + h.Write(data) + } +} + +func BenchmarkWrite128Generic(b *testing.B) { benchmarkWrite(b, 128, false, false, false) } +func BenchmarkWrite1KGeneric(b *testing.B) { benchmarkWrite(b, 1024, false, false, false) } +func BenchmarkWrite128SSE4(b *testing.B) { benchmarkWrite(b, 128, true, false, false) } +func BenchmarkWrite1KSSE4(b *testing.B) { benchmarkWrite(b, 1024, true, false, false) } +func BenchmarkWrite128AVX(b *testing.B) { benchmarkWrite(b, 128, false, true, false) } +func BenchmarkWrite1KAVX(b *testing.B) { benchmarkWrite(b, 1024, false, true, false) } +func BenchmarkWrite128AVX2(b *testing.B) { benchmarkWrite(b, 128, false, false, true) } +func BenchmarkWrite1KAVX2(b *testing.B) { benchmarkWrite(b, 1024, false, false, true) } + +func BenchmarkSum128Generic(b *testing.B) { benchmarkSum(b, 128, false, false, false) } +func BenchmarkSum1KGeneric(b *testing.B) { benchmarkSum(b, 1024, false, false, false) } +func BenchmarkSum128SSE4(b *testing.B) { benchmarkSum(b, 128, true, false, false) } +func BenchmarkSum1KSSE4(b *testing.B) { benchmarkSum(b, 1024, true, false, false) } +func BenchmarkSum128AVX(b *testing.B) { benchmarkSum(b, 128, false, true, false) } +func BenchmarkSum1KAVX(b *testing.B) { benchmarkSum(b, 1024, false, true, false) } +func BenchmarkSum128AVX2(b *testing.B) { benchmarkSum(b, 128, false, false, true) } +func BenchmarkSum1KAVX2(b *testing.B) { benchmarkSum(b, 1024, false, false, true) } + +// These values were taken from https://blake2.net/blake2b-test.txt. +var hashes = []string{ + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd", + "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + "beaa5a3d08f3807143cf621d95cd690514d0b49efff9c91d24b59241ec0eefa5f60196d407048bba8d2146828ebcb0488d8842fd56bb4f6df8e19c4b4daab8ac", + "098084b51fd13deae5f4320de94a688ee07baea2800486689a8636117b46c1f4c1f6af7f74ae7c857600456a58a3af251dc4723a64cc7c0a5ab6d9cac91c20bb", + "6044540d560853eb1c57df0077dd381094781cdb9073e5b1b3d3f6c7829e12066bbaca96d989a690de72ca3133a83652ba284a6d62942b271ffa2620c9e75b1f", + "7a8cfe9b90f75f7ecb3acc053aaed6193112b6f6a4aeeb3f65d3de541942deb9e2228152a3c4bbbe72fc3b12629528cfbb09fe630f0474339f54abf453e2ed52", + "380beaf6ea7cc9365e270ef0e6f3a64fb902acae51dd5512f84259ad2c91f4bc4108db73192a5bbfb0cbcf71e46c3e21aee1c5e860dc96e8eb0b7b8426e6abe9", + "60fe3c4535e1b59d9a61ea8500bfac41a69dffb1ceadd9aca323e9a625b64da5763bad7226da02b9c8c4f1a5de140ac5a6c1124e4f718ce0b28ea47393aa6637", + "4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd", + "f228773ce3f3a42b5f144d63237a72d99693adb8837d0e112a8a0f8ffff2c362857ac49c11ec740d1500749dac9b1f4548108bf3155794dcc9e4082849e2b85b", + "962452a8455cc56c8511317e3b1f3b2c37df75f588e94325fdd77070359cf63a9ae6e930936fdf8e1e08ffca440cfb72c28f06d89a2151d1c46cd5b268ef8563", + "43d44bfa18768c59896bf7ed1765cb2d14af8c260266039099b25a603e4ddc5039d6ef3a91847d1088d401c0c7e847781a8a590d33a3c6cb4df0fab1c2f22355", + "dcffa9d58c2a4ca2cdbb0c7aa4c4c1d45165190089f4e983bb1c2cab4aaeff1fa2b5ee516fecd780540240bf37e56c8bcca7fab980e1e61c9400d8a9a5b14ac6", + "6fbf31b45ab0c0b8dad1c0f5f4061379912dde5aa922099a030b725c73346c524291adef89d2f6fd8dfcda6d07dad811a9314536c2915ed45da34947e83de34e", + "a0c65bddde8adef57282b04b11e7bc8aab105b99231b750c021f4a735cb1bcfab87553bba3abb0c3e64a0b6955285185a0bd35fb8cfde557329bebb1f629ee93", + "f99d815550558e81eca2f96718aed10d86f3f1cfb675cce06b0eff02f617c5a42c5aa760270f2679da2677c5aeb94f1142277f21c7f79f3c4f0cce4ed8ee62b1", + "95391da8fc7b917a2044b3d6f5374e1ca072b41454d572c7356c05fd4bc1e0f40b8bb8b4a9f6bce9be2c4623c399b0dca0dab05cb7281b71a21b0ebcd9e55670", + "04b9cd3d20d221c09ac86913d3dc63041989a9a1e694f1e639a3ba7e451840f750c2fc191d56ad61f2e7936bc0ac8e094b60caeed878c18799045402d61ceaf9", + "ec0e0ef707e4ed6c0c66f9e089e4954b058030d2dd86398fe84059631f9ee591d9d77375355149178c0cf8f8e7c49ed2a5e4f95488a2247067c208510fadc44c", + "9a37cce273b79c09913677510eaf7688e89b3314d3532fd2764c39de022a2945b5710d13517af8ddc0316624e73bec1ce67df15228302036f330ab0cb4d218dd", + "4cf9bb8fb3d4de8b38b2f262d3c40f46dfe747e8fc0a414c193d9fcf753106ce47a18f172f12e8a2f1c26726545358e5ee28c9e2213a8787aafbc516d2343152", + "64e0c63af9c808fd893137129867fd91939d53f2af04be4fa268006100069b2d69daa5c5d8ed7fddcb2a70eeecdf2b105dd46a1e3b7311728f639ab489326bc9", + "5e9c93158d659b2def06b0c3c7565045542662d6eee8a96a89b78ade09fe8b3dcc096d4fe48815d88d8f82620156602af541955e1f6ca30dce14e254c326b88f", + "7775dff889458dd11aef417276853e21335eb88e4dec9cfb4e9edb49820088551a2ca60339f12066101169f0dfe84b098fddb148d9da6b3d613df263889ad64b", + "f0d2805afbb91f743951351a6d024f9353a23c7ce1fc2b051b3a8b968c233f46f50f806ecb1568ffaa0b60661e334b21dde04f8fa155ac740eeb42e20b60d764", + "86a2af316e7d7754201b942e275364ac12ea8962ab5bd8d7fb276dc5fbffc8f9a28cae4e4867df6780d9b72524160927c855da5b6078e0b554aa91e31cb9ca1d", + "10bdf0caa0802705e706369baf8a3f79d72c0a03a80675a7bbb00be3a45e516424d1ee88efb56f6d5777545ae6e27765c3a8f5e493fc308915638933a1dfee55", + "b01781092b1748459e2e4ec178696627bf4ebafebba774ecf018b79a68aeb84917bf0b84bb79d17b743151144cd66b7b33a4b9e52c76c4e112050ff5385b7f0b", + "c6dbc61dec6eaeac81e3d5f755203c8e220551534a0b2fd105a91889945a638550204f44093dd998c076205dffad703a0e5cd3c7f438a7e634cd59fededb539e", + "eba51acffb4cea31db4b8d87e9bf7dd48fe97b0253ae67aa580f9ac4a9d941f2bea518ee286818cc9f633f2a3b9fb68e594b48cdd6d515bf1d52ba6c85a203a7", + "86221f3ada52037b72224f105d7999231c5e5534d03da9d9c0a12acb68460cd375daf8e24386286f9668f72326dbf99ba094392437d398e95bb8161d717f8991", + "5595e05c13a7ec4dc8f41fb70cb50a71bce17c024ff6de7af618d0cc4e9c32d9570d6d3ea45b86525491030c0d8f2b1836d5778c1ce735c17707df364d054347", + "ce0f4f6aca89590a37fe034dd74dd5fa65eb1cbd0a41508aaddc09351a3cea6d18cb2189c54b700c009f4cbf0521c7ea01be61c5ae09cb54f27bc1b44d658c82", + "7ee80b06a215a3bca970c77cda8761822bc103d44fa4b33f4d07dcb997e36d55298bceae12241b3fa07fa63be5576068da387b8d5859aeab701369848b176d42", + "940a84b6a84d109aab208c024c6ce9647676ba0aaa11f86dbb7018f9fd2220a6d901a9027f9abcf935372727cbf09ebd61a2a2eeb87653e8ecad1bab85dc8327", + "2020b78264a82d9f4151141adba8d44bf20c5ec062eee9b595a11f9e84901bf148f298e0c9f8777dcdbc7cc4670aac356cc2ad8ccb1629f16f6a76bcefbee760", + "d1b897b0e075ba68ab572adf9d9c436663e43eb3d8e62d92fc49c9be214e6f27873fe215a65170e6bea902408a25b49506f47babd07cecf7113ec10c5dd31252", + "b14d0c62abfa469a357177e594c10c194243ed2025ab8aa5ad2fa41ad318e0ff48cd5e60bec07b13634a711d2326e488a985f31e31153399e73088efc86a5c55", + "4169c5cc808d2697dc2a82430dc23e3cd356dc70a94566810502b8d655b39abf9e7f902fe717e0389219859e1945df1af6ada42e4ccda55a197b7100a30c30a1", + "258a4edb113d66c839c8b1c91f15f35ade609f11cd7f8681a4045b9fef7b0b24c82cda06a5f2067b368825e3914e53d6948ede92efd6e8387fa2e537239b5bee", + "79d2d8696d30f30fb34657761171a11e6c3f1e64cbe7bebee159cb95bfaf812b4f411e2f26d9c421dc2c284a3342d823ec293849e42d1e46b0a4ac1e3c86abaa", + "8b9436010dc5dee992ae38aea97f2cd63b946d94fedd2ec9671dcde3bd4ce9564d555c66c15bb2b900df72edb6b891ebcadfeff63c9ea4036a998be7973981e7", + "c8f68e696ed28242bf997f5b3b34959508e42d613810f1e2a435c96ed2ff560c7022f361a9234b9837feee90bf47922ee0fd5f8ddf823718d86d1e16c6090071", + "b02d3eee4860d5868b2c39ce39bfe81011290564dd678c85e8783f29302dfc1399ba95b6b53cd9ebbf400cca1db0ab67e19a325f2d115812d25d00978ad1bca4", + "7693ea73af3ac4dad21ca0d8da85b3118a7d1c6024cfaf557699868217bc0c2f44a199bc6c0edd519798ba05bd5b1b4484346a47c2cadf6bf30b785cc88b2baf", + "a0e5c1c0031c02e48b7f09a5e896ee9aef2f17fc9e18e997d7f6cac7ae316422c2b1e77984e5f3a73cb45deed5d3f84600105e6ee38f2d090c7d0442ea34c46d", + "41daa6adcfdb69f1440c37b596440165c15ada596813e2e22f060fcd551f24dee8e04ba6890387886ceec4a7a0d7fc6b44506392ec3822c0d8c1acfc7d5aebe8", + "14d4d40d5984d84c5cf7523b7798b254e275a3a8cc0a1bd06ebc0bee726856acc3cbf516ff667cda2058ad5c3412254460a82c92187041363cc77a4dc215e487", + "d0e7a1e2b9a447fee83e2277e9ff8010c2f375ae12fa7aaa8ca5a6317868a26a367a0b69fbc1cf32a55d34eb370663016f3d2110230eba754028a56f54acf57c", + "e771aa8db5a3e043e8178f39a0857ba04a3f18e4aa05743cf8d222b0b095825350ba422f63382a23d92e4149074e816a36c1cd28284d146267940b31f8818ea2", + "feb4fd6f9e87a56bef398b3284d2bda5b5b0e166583a66b61e538457ff0584872c21a32962b9928ffab58de4af2edd4e15d8b35570523207ff4e2a5aa7754caa", + "462f17bf005fb1c1b9e671779f665209ec2873e3e411f98dabf240a1d5ec3f95ce6796b6fc23fe171903b502023467dec7273ff74879b92967a2a43a5a183d33", + "d3338193b64553dbd38d144bea71c5915bb110e2d88180dbc5db364fd6171df317fc7268831b5aef75e4342b2fad8797ba39eddcef80e6ec08159350b1ad696d", + "e1590d585a3d39f7cb599abd479070966409a6846d4377acf4471d065d5db94129cc9be92573b05ed226be1e9b7cb0cabe87918589f80dadd4ef5ef25a93d28e", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e76842d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + "30186055c07949948183c850e9a756cc09937e247d9d928e869e20bafc3cd9721719d34e04a0899b92c736084550186886efba2e790d8be6ebf040b209c439a4", + "f3c4276cb863637712c241c444c5cc1e3554e0fddb174d035819dd83eb700b4ce88df3ab3841ba02085e1a99b4e17310c5341075c0458ba376c95a6818fbb3e2", + "0aa007c4dd9d5832393040a1583c930bca7dc5e77ea53add7e2b3f7c8e231368043520d4a3ef53c969b6bbfd025946f632bd7f765d53c21003b8f983f75e2a6a", + "08e9464720533b23a04ec24f7ae8c103145f765387d738777d3d343477fd1c58db052142cab754ea674378e18766c53542f71970171cc4f81694246b717d7564", + "d37ff7ad297993e7ec21e0f1b4b5ae719cdc83c5db687527f27516cbffa822888a6810ee5c1ca7bfe3321119be1ab7bfa0a502671c8329494df7ad6f522d440f", + "dd9042f6e464dcf86b1262f6accfafbd8cfd902ed3ed89abf78ffa482dbdeeb6969842394c9a1168ae3d481a017842f660002d42447c6b22f7b72f21aae021c9", + "bd965bf31e87d70327536f2a341cebc4768eca275fa05ef98f7f1b71a0351298de006fba73fe6733ed01d75801b4a928e54231b38e38c562b2e33ea1284992fa", + "65676d800617972fbd87e4b9514e1c67402b7a331096d3bfac22f1abb95374abc942f16e9ab0ead33b87c91968a6e509e119ff07787b3ef483e1dcdccf6e3022", + "939fa189699c5d2c81ddd1ffc1fa207c970b6a3685bb29ce1d3e99d42f2f7442da53e95a72907314f4588399a3ff5b0a92beb3f6be2694f9f86ecf2952d5b41c", + "c516541701863f91005f314108ceece3c643e04fc8c42fd2ff556220e616aaa6a48aeb97a84bad74782e8dff96a1a2fa949339d722edcaa32b57067041df88cc", + "987fd6e0d6857c553eaebb3d34970a2c2f6e89a3548f492521722b80a1c21a153892346d2cba6444212d56da9a26e324dccbc0dcde85d4d2ee4399eec5a64e8f", + "ae56deb1c2328d9c4017706bce6e99d41349053ba9d336d677c4c27d9fd50ae6aee17e853154e1f4fe7672346da2eaa31eea53fcf24a22804f11d03da6abfc2b", + "49d6a608c9bde4491870498572ac31aac3fa40938b38a7818f72383eb040ad39532bc06571e13d767e6945ab77c0bdc3b0284253343f9f6c1244ebf2ff0df866", + "da582ad8c5370b4469af862aa6467a2293b2b28bd80ae0e91f425ad3d47249fdf98825cc86f14028c3308c9804c78bfeeeee461444ce243687e1a50522456a1d", + "d5266aa3331194aef852eed86d7b5b2633a0af1c735906f2e13279f14931a9fc3b0eac5ce9245273bd1aa92905abe16278ef7efd47694789a7283b77da3c70f8", + "2962734c28252186a9a1111c732ad4de4506d4b4480916303eb7991d659ccda07a9911914bc75c418ab7a4541757ad054796e26797feaf36e9f6ad43f14b35a4", + "e8b79ec5d06e111bdfafd71e9f5760f00ac8ac5d8bf768f9ff6f08b8f026096b1cc3a4c973333019f1e3553e77da3f98cb9f542e0a90e5f8a940cc58e59844b3", + "dfb320c44f9d41d1efdcc015f08dd5539e526e39c87d509ae6812a969e5431bf4fa7d91ffd03b981e0d544cf72d7b1c0374f8801482e6dea2ef903877eba675e", + "d88675118fdb55a5fb365ac2af1d217bf526ce1ee9c94b2f0090b2c58a06ca58187d7fe57c7bed9d26fca067b4110eefcd9a0a345de872abe20de368001b0745", + "b893f2fc41f7b0dd6e2f6aa2e0370c0cff7df09e3acfcc0e920b6e6fad0ef747c40668417d342b80d2351e8c175f20897a062e9765e6c67b539b6ba8b9170545", + "6c67ec5697accd235c59b486d7b70baeedcbd4aa64ebd4eef3c7eac189561a726250aec4d48cadcafbbe2ce3c16ce2d691a8cce06e8879556d4483ed7165c063", + "f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd", + "cbaa259572d4aebfc1917acddc582b9f8dfaa928a198ca7acd0f2aa76a134a90252e6298a65b08186a350d5b7626699f8cb721a3ea5921b753ae3a2dce24ba3a", + "fa1549c9796cd4d303dcf452c1fbd5744fd9b9b47003d920b92de34839d07ef2a29ded68f6fc9e6c45e071a2e48bd50c5084e96b657dd0404045a1ddefe282ed", + "5cf2ac897ab444dcb5c8d87c495dbdb34e1838b6b629427caa51702ad0f9688525f13bec503a3c3a2c80a65e0b5715e8afab00ffa56ec455a49a1ad30aa24fcd", + "9aaf80207bace17bb7ab145757d5696bde32406ef22b44292ef65d4519c3bb2ad41a59b62cc3e94b6fa96d32a7faadae28af7d35097219aa3fd8cda31e40c275", + "af88b163402c86745cb650c2988fb95211b94b03ef290eed9662034241fd51cf398f8073e369354c43eae1052f9b63b08191caa138aa54fea889cc7024236897", + "48fa7d64e1ceee27b9864db5ada4b53d00c9bc7626555813d3cd6730ab3cc06ff342d727905e33171bde6e8476e77fb1720861e94b73a2c538d254746285f430", + "0e6fd97a85e904f87bfe85bbeb34f69e1f18105cf4ed4f87aec36c6e8b5f68bd2a6f3dc8a9ecb2b61db4eedb6b2ea10bf9cb0251fb0f8b344abf7f366b6de5ab", + "06622da5787176287fdc8fed440bad187d830099c94e6d04c8e9c954cda70c8bb9e1fc4a6d0baa831b9b78ef6648681a4867a11da93ee36e5e6a37d87fc63f6f", + "1da6772b58fabf9c61f68d412c82f182c0236d7d575ef0b58dd22458d643cd1dfc93b03871c316d8430d312995d4197f0874c99172ba004a01ee295abac24e46", + "3cd2d9320b7b1d5fb9aab951a76023fa667be14a9124e394513918a3f44096ae4904ba0ffc150b63bc7ab1eeb9a6e257e5c8f000a70394a5afd842715de15f29", + "04cdc14f7434e0b4be70cb41db4c779a88eaef6accebcb41f2d42fffe7f32a8e281b5c103a27021d0d08362250753cdf70292195a53a48728ceb5844c2d98bab", + "9071b7a8a075d0095b8fb3ae5113785735ab98e2b52faf91d5b89e44aac5b5d4ebbf91223b0ff4c71905da55342e64655d6ef8c89a4768c3f93a6dc0366b5bc8", + "ebb30240dd96c7bc8d0abe49aa4edcbb4afdc51ff9aaf720d3f9e7fbb0f9c6d6571350501769fc4ebd0b2141247ff400d4fd4be414edf37757bb90a32ac5c65a", + "8532c58bf3c8015d9d1cbe00eef1f5082f8f3632fbe9f1ed4f9dfb1fa79e8283066d77c44c4af943d76b300364aecbd0648c8a8939bd204123f4b56260422dec", + "fe9846d64f7c7708696f840e2d76cb4408b6595c2f81ec6a28a7f2f20cb88cfe6ac0b9e9b8244f08bd7095c350c1d0842f64fb01bb7f532dfcd47371b0aeeb79", + "28f17ea6fb6c42092dc264257e29746321fb5bdaea9873c2a7fa9d8f53818e899e161bc77dfe8090afd82bf2266c5c1bc930a8d1547624439e662ef695f26f24", + "ec6b7d7f030d4850acae3cb615c21dd25206d63e84d1db8d957370737ba0e98467ea0ce274c66199901eaec18a08525715f53bfdb0aacb613d342ebdceeddc3b", + "b403d3691c03b0d3418df327d5860d34bbfcc4519bfbce36bf33b208385fadb9186bc78a76c489d89fd57e7dc75412d23bcd1dae8470ce9274754bb8585b13c5", + "31fc79738b8772b3f55cd8178813b3b52d0db5a419d30ba9495c4b9da0219fac6df8e7c23a811551a62b827f256ecdb8124ac8a6792ccfecc3b3012722e94463", + "bb2039ec287091bcc9642fc90049e73732e02e577e2862b32216ae9bedcd730c4c284ef3968c368b7d37584f97bd4b4dc6ef6127acfe2e6ae2509124e66c8af4", + "f53d68d13f45edfcb9bd415e2831e938350d5380d3432278fc1c0c381fcb7c65c82dafe051d8c8b0d44e0974a0e59ec7bf7ed0459f86e96f329fc79752510fd3", + "8d568c7984f0ecdf7640fbc483b5d8c9f86634f6f43291841b309a350ab9c1137d24066b09da9944bac54d5bb6580d836047aac74ab724b887ebf93d4b32eca9", + "c0b65ce5a96ff774c456cac3b5f2c4cd359b4ff53ef93a3da0778be4900d1e8da1601e769e8f1b02d2a2f8c5b9fa10b44f1c186985468feeb008730283a6657d", + "4900bba6f5fb103ece8ec96ada13a5c3c85488e05551da6b6b33d988e611ec0fe2e3c2aa48ea6ae8986a3a231b223c5d27cec2eadde91ce07981ee652862d1e4", + "c7f5c37c7285f927f76443414d4357ff789647d7a005a5a787e03c346b57f49f21b64fa9cf4b7e45573e23049017567121a9c3d4b2b73ec5e9413577525db45a", + "ec7096330736fdb2d64b5653e7475da746c23a4613a82687a28062d3236364284ac01720ffb406cfe265c0df626a188c9e5963ace5d3d5bb363e32c38c2190a6", + "82e744c75f4649ec52b80771a77d475a3bc091989556960e276a5f9ead92a03f718742cdcfeaee5cb85c44af198adc43a4a428f5f0c2ddb0be36059f06d7df73", + "2834b7a7170f1f5b68559ab78c1050ec21c919740b784a9072f6e5d69f828d70c919c5039fb148e39e2c8a52118378b064ca8d5001cd10a5478387b966715ed6", + "16b4ada883f72f853bb7ef253efcab0c3e2161687ad61543a0d2824f91c1f81347d86be709b16996e17f2dd486927b0288ad38d13063c4a9672c39397d3789b6", + "78d048f3a69d8b54ae0ed63a573ae350d89f7c6cf1f3688930de899afa037697629b314e5cd303aa62feea72a25bf42b304b6c6bcb27fae21c16d925e1fbdac3", + "0f746a48749287ada77a82961f05a4da4abdb7d77b1220f836d09ec814359c0ec0239b8c7b9ff9e02f569d1b301ef67c4612d1de4f730f81c12c40cc063c5caa", + "f0fc859d3bd195fbdc2d591e4cdac15179ec0f1dc821c11df1f0c1d26e6260aaa65b79fafacafd7d3ad61e600f250905f5878c87452897647a35b995bcadc3a3", + "2620f687e8625f6a412460b42e2cef67634208ce10a0cbd4dff7044a41b7880077e9f8dc3b8d1216d3376a21e015b58fb279b521d83f9388c7382c8505590b9b", + "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c528fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f", + "1a929901b09c25f27d6b35be7b2f1c4745131fdebca7f3e2451926720434e0db6e74fd693ad29b777dc3355c592a361c4873b01133a57c2e3b7075cbdb86f4fc", + "5fd7968bc2fe34f220b5e3dc5af9571742d73b7d60819f2888b629072b96a9d8ab2d91b82d0a9aaba61bbd39958132fcc4257023d1eca591b3054e2dc81c8200", + "dfcce8cf32870cc6a503eadafc87fd6f78918b9b4d0737db6810be996b5497e7e5cc80e312f61e71ff3e9624436073156403f735f56b0b01845c18f6caf772e6", + "02f7ef3a9ce0fff960f67032b296efca3061f4934d690749f2d01c35c81c14f39a67fa350bc8a0359bf1724bffc3bca6d7c7bba4791fd522a3ad353c02ec5aa8", + "64be5c6aba65d594844ae78bb022e5bebe127fd6b6ffa5a13703855ab63b624dcd1a363f99203f632ec386f3ea767fc992e8ed9686586aa27555a8599d5b808f", + "f78585505c4eaa54a8b5be70a61e735e0ff97af944ddb3001e35d86c4e2199d976104b6ae31750a36a726ed285064f5981b503889fef822fcdc2898dddb7889a", + "e4b5566033869572edfd87479a5bb73c80e8759b91232879d96b1dda36c012076ee5a2ed7ae2de63ef8406a06aea82c188031b560beafb583fb3de9e57952a7e", + "e1b3e7ed867f6c9484a2a97f7715f25e25294e992e41f6a7c161ffc2adc6daaeb7113102d5e6090287fe6ad94ce5d6b739c6ca240b05c76fb73f25dd024bf935", + "85fd085fdc12a080983df07bd7012b0d402a0f4043fcb2775adf0bad174f9b08d1676e476985785c0a5dcc41dbff6d95ef4d66a3fbdc4a74b82ba52da0512b74", + "aed8fa764b0fbff821e05233d2f7b0900ec44d826f95e93c343c1bc3ba5a24374b1d616e7e7aba453a0ada5e4fab5382409e0d42ce9c2bc7fb39a99c340c20f0", + "7ba3b2e297233522eeb343bd3ebcfd835a04007735e87f0ca300cbee6d416565162171581e4020ff4cf176450f1291ea2285cb9ebffe4c56660627685145051c", + "de748bcf89ec88084721e16b85f30adb1a6134d664b5843569babc5bbd1a15ca9b61803c901a4fef32965a1749c9f3a4e243e173939dc5a8dc495c671ab52145", + "aaf4d2bdf200a919706d9842dce16c98140d34bc433df320aba9bd429e549aa7a3397652a4d768277786cf993cde2338673ed2e6b66c961fefb82cd20c93338f", + "c408218968b788bf864f0997e6bc4c3dba68b276e2125a4843296052ff93bf5767b8cdce7131f0876430c1165fec6c4f47adaa4fd8bcfacef463b5d3d0fa61a0", + "76d2d819c92bce55fa8e092ab1bf9b9eab237a25267986cacf2b8ee14d214d730dc9a5aa2d7b596e86a1fd8fa0804c77402d2fcd45083688b218b1cdfa0dcbcb", + "72065ee4dd91c2d8509fa1fc28a37c7fc9fa7d5b3f8ad3d0d7a25626b57b1b44788d4caf806290425f9890a3a2a35a905ab4b37acfd0da6e4517b2525c9651e4", + "64475dfe7600d7171bea0b394e27c9b00d8e74dd1e416a79473682ad3dfdbb706631558055cfc8a40e07bd015a4540dcdea15883cbbf31412df1de1cd4152b91", + "12cd1674a4488a5d7c2b3160d2e2c4b58371bedad793418d6f19c6ee385d70b3e06739369d4df910edb0b0a54cbff43d54544cd37ab3a06cfa0a3ddac8b66c89", + "60756966479dedc6dd4bcff8ea7d1d4ce4d4af2e7b097e32e3763518441147cc12b3c0ee6d2ecabf1198cec92e86a3616fba4f4e872f5825330adbb4c1dee444", + "a7803bcb71bc1d0f4383dde1e0612e04f872b715ad30815c2249cf34abb8b024915cb2fc9f4e7cc4c8cfd45be2d5a91eab0941c7d270e2da4ca4a9f7ac68663a", + "b84ef6a7229a34a750d9a98ee2529871816b87fbe3bc45b45fa5ae82d5141540211165c3c5d7a7476ba5a4aa06d66476f0d9dc49a3f1ee72c3acabd498967414", + "fae4b6d8efc3f8c8e64d001dabec3a21f544e82714745251b2b4b393f2f43e0da3d403c64db95a2cb6e23ebb7b9e94cdd5ddac54f07c4a61bd3cb10aa6f93b49", + "34f7286605a122369540141ded79b8957255da2d4155abbf5a8dbb89c8eb7ede8eeef1daa46dc29d751d045dc3b1d658bb64b80ff8589eddb3824b13da235a6b", + "3b3b48434be27b9eababba43bf6b35f14b30f6a88dc2e750c358470d6b3aa3c18e47db4017fa55106d8252f016371a00f5f8b070b74ba5f23cffc5511c9f09f0", + "ba289ebd6562c48c3e10a8ad6ce02e73433d1e93d7c9279d4d60a7e879ee11f441a000f48ed9f7c4ed87a45136d7dccdca482109c78a51062b3ba4044ada2469", + "022939e2386c5a37049856c850a2bb10a13dfea4212b4c732a8840a9ffa5faf54875c5448816b2785a007da8a8d2bc7d71a54e4e6571f10b600cbdb25d13ede3", + "e6fec19d89ce8717b1a087024670fe026f6c7cbda11caef959bb2d351bf856f8055d1c0ebdaaa9d1b17886fc2c562b5e99642fc064710c0d3488a02b5ed7f6fd", + "94c96f02a8f576aca32ba61c2b206f907285d9299b83ac175c209a8d43d53bfe683dd1d83e7549cb906c28f59ab7c46f8751366a28c39dd5fe2693c9019666c8", + "31a0cd215ebd2cb61de5b9edc91e6195e31c59a5648d5c9f737e125b2605708f2e325ab3381c8dce1a3e958886f1ecdc60318f882cfe20a24191352e617b0f21", + "91ab504a522dce78779f4c6c6ba2e6b6db5565c76d3e7e7c920caf7f757ef9db7c8fcf10e57f03379ea9bf75eb59895d96e149800b6aae01db778bb90afbc989", + "d85cabc6bd5b1a01a5afd8c6734740da9fd1c1acc6db29bfc8a2e5b668b028b6b3154bfb8703fa3180251d589ad38040ceb707c4bad1b5343cb426b61eaa49c1", + "d62efbec2ca9c1f8bd66ce8b3f6a898cb3f7566ba6568c618ad1feb2b65b76c3ce1dd20f7395372faf28427f61c9278049cf0140df434f5633048c86b81e0399", + "7c8fdc6175439e2c3db15bafa7fb06143a6a23bc90f449e79deef73c3d492a671715c193b6fea9f036050b946069856b897e08c00768f5ee5ddcf70b7cd6d0e0", + "58602ee7468e6bc9df21bd51b23c005f72d6cb013f0a1b48cbec5eca299299f97f09f54a9a01483eaeb315a6478bad37ba47ca1347c7c8fc9e6695592c91d723", + "27f5b79ed256b050993d793496edf4807c1d85a7b0a67c9c4fa99860750b0ae66989670a8ffd7856d7ce411599e58c4d77b232a62bef64d15275be46a68235ff", + "3957a976b9f1887bf004a8dca942c92d2b37ea52600f25e0c9bc5707d0279c00c6e85a839b0d2d8eb59c51d94788ebe62474a791cadf52cccf20f5070b6573fc", + "eaa2376d55380bf772ecca9cb0aa4668c95c707162fa86d518c8ce0ca9bf7362b9f2a0adc3ff59922df921b94567e81e452f6c1a07fc817cebe99604b3505d38", + "c1e2c78b6b2734e2480ec550434cb5d613111adcc21d475545c3b1b7e6ff12444476e5c055132e2229dc0f807044bb919b1a5662dd38a9ee65e243a3911aed1a", + "8ab48713389dd0fcf9f965d3ce66b1e559a1f8c58741d67683cd971354f452e62d0207a65e436c5d5d8f8ee71c6abfe50e669004c302b31a7ea8311d4a916051", + "24ce0addaa4c65038bd1b1c0f1452a0b128777aabc94a29df2fd6c7e2f85f8ab9ac7eff516b0e0a825c84a24cfe492eaad0a6308e46dd42fe8333ab971bb30ca", + "5154f929ee03045b6b0c0004fa778edee1d139893267cc84825ad7b36c63de32798e4a166d24686561354f63b00709a1364b3c241de3febf0754045897467cd4", + "e74e907920fd87bd5ad636dd11085e50ee70459c443e1ce5809af2bc2eba39f9e6d7128e0e3712c316da06f4705d78a4838e28121d4344a2c79c5e0db307a677", + "bf91a22334bac20f3fd80663b3cd06c4e8802f30e6b59f90d3035cc9798a217ed5a31abbda7fa6842827bdf2a7a1c21f6fcfccbb54c6c52926f32da816269be1", + "d9d5c74be5121b0bd742f26bffb8c89f89171f3f934913492b0903c271bbe2b3395ef259669bef43b57f7fcc3027db01823f6baee66e4f9fead4d6726c741fce", + "50c8b8cf34cd879f80e2faab3230b0c0e1cc3e9dcadeb1b9d97ab923415dd9a1fe38addd5c11756c67990b256e95ad6d8f9fedce10bf1c90679cde0ecf1be347", + "0a386e7cd5dd9b77a035e09fe6fee2c8ce61b5383c87ea43205059c5e4cd4f4408319bb0a82360f6a58e6c9ce3f487c446063bf813bc6ba535e17fc1826cfc91", + "1f1459cb6b61cbac5f0efe8fc487538f42548987fcd56221cfa7beb22504769e792c45adfb1d6b3d60d7b749c8a75b0bdf14e8ea721b95dca538ca6e25711209", + "e58b3836b7d8fedbb50ca5725c6571e74c0785e97821dab8b6298c10e4c079d4a6cdf22f0fedb55032925c16748115f01a105e77e00cee3d07924dc0d8f90659", + "b929cc6505f020158672deda56d0db081a2ee34c00c1100029bdf8ea98034fa4bf3e8655ec697fe36f40553c5bb46801644a627d3342f4fc92b61f03290fb381", + "72d353994b49d3e03153929a1e4d4f188ee58ab9e72ee8e512f29bc773913819ce057ddd7002c0433ee0a16114e3d156dd2c4a7e80ee53378b8670f23e33ef56", + "c70ef9bfd775d408176737a0736d68517ce1aaad7e81a93c8c1ed967ea214f56c8a377b1763e676615b60f3988241eae6eab9685a5124929d28188f29eab06f7", + "c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f", + "6f43094cafb5ebf1f7a4937ec50f56a4c9da303cbb55ac1f27f1f1976cd96beda9464f0e7b9c54620b8a9fba983164b8be3578425a024f5fe199c36356b88972", + "3745273f4c38225db2337381871a0c6aafd3af9b018c88aa02025850a5dc3a42a1a3e03e56cbf1b0876d63a441f1d2856a39b8801eb5af325201c415d65e97fe", + "c50c44cca3ec3edaae779a7e179450ebdda2f97067c690aa6c5a4ac7c30139bb27c0df4db3220e63cb110d64f37ffe078db72653e2daacf93ae3f0a2d1a7eb2e", + "8aef263e385cbc61e19b28914243262af5afe8726af3ce39a79c27028cf3ecd3f8d2dfd9cfc9ad91b58f6f20778fd5f02894a3d91c7d57d1e4b866a7f364b6be", + "28696141de6e2d9bcb3235578a66166c1448d3e905a1b482d423be4bc5369bc8c74dae0acc9cc123e1d8ddce9f97917e8c019c552da32d39d2219b9abf0fa8c8", + "2fb9eb2085830181903a9dafe3db428ee15be7662224efd643371fb25646aee716e531eca69b2bdc8233f1a8081fa43da1500302975a77f42fa592136710e9dc", + "66f9a7143f7a3314a669bf2e24bbb35014261d639f495b6c9c1f104fe8e320aca60d4550d69d52edbd5a3cdeb4014ae65b1d87aa770b69ae5c15f4330b0b0ad8", + "f4c4dd1d594c3565e3e25ca43dad82f62abea4835ed4cd811bcd975e46279828d44d4c62c3679f1b7f7b9dd4571d7b49557347b8c5460cbdc1bef690fb2a08c0", + "8f1dc9649c3a84551f8f6e91cac68242a43b1f8f328ee92280257387fa7559aa6db12e4aeadc2d26099178749c6864b357f3f83b2fb3efa8d2a8db056bed6bcc", + "3139c1a7f97afd1675d460ebbc07f2728aa150df849624511ee04b743ba0a833092f18c12dc91b4dd243f333402f59fe28abdbbbae301e7b659c7a26d5c0f979", + "06f94a2996158a819fe34c40de3cf0379fd9fb85b3e363ba3926a0e7d960e3f4c2e0c70c7ce0ccb2a64fc29869f6e7ab12bd4d3f14fce943279027e785fb5c29", + "c29c399ef3eee8961e87565c1ce263925fc3d0ce267d13e48dd9e732ee67b0f69fad56401b0f10fcaac119201046cca28c5b14abdea3212ae65562f7f138db3d", + "4cec4c9df52eef05c3f6faaa9791bc7445937183224ecc37a1e58d0132d35617531d7e795f52af7b1eb9d147de1292d345fe341823f8e6bc1e5badca5c656108", + "898bfbae93b3e18d00697eab7d9704fa36ec339d076131cefdf30edbe8d9cc81c3a80b129659b163a323bab9793d4feed92d54dae966c77529764a09be88db45", + "ee9bd0469d3aaf4f14035be48a2c3b84d9b4b1fff1d945e1f1c1d38980a951be197b25fe22c731f20aeacc930ba9c4a1f4762227617ad350fdabb4e80273a0f4", + "3d4d3113300581cd96acbf091c3d0f3c310138cd6979e6026cde623e2dd1b24d4a8638bed1073344783ad0649cc6305ccec04beb49f31c633088a99b65130267", + "95c0591ad91f921ac7be6d9ce37e0663ed8011c1cfd6d0162a5572e94368bac02024485e6a39854aa46fe38e97d6c6b1947cd272d86b06bb5b2f78b9b68d559d", + "227b79ded368153bf46c0a3ca978bfdbef31f3024a5665842468490b0ff748ae04e7832ed4c9f49de9b1706709d623e5c8c15e3caecae8d5e433430ff72f20eb", + "5d34f3952f0105eef88ae8b64c6ce95ebfade0e02c69b08762a8712d2e4911ad3f941fc4034dc9b2e479fdbcd279b902faf5d838bb2e0c6495d372b5b7029813", + "7f939bf8353abce49e77f14f3750af20b7b03902e1a1e7fb6aaf76d0259cd401a83190f15640e74f3e6c5a90e839c7821f6474757f75c7bf9002084ddc7a62dc", + "062b61a2f9a33a71d7d0a06119644c70b0716a504de7e5e1be49bd7b86e7ed6817714f9f0fc313d06129597e9a2235ec8521de36f7290a90ccfc1ffa6d0aee29", + "f29e01eeae64311eb7f1c6422f946bf7bea36379523e7b2bbaba7d1d34a22d5ea5f1c5a09d5ce1fe682cced9a4798d1a05b46cd72dff5c1b355440b2a2d476bc", + "ec38cd3bbab3ef35d7cb6d5c914298351d8a9dc97fcee051a8a02f58e3ed6184d0b7810a5615411ab1b95209c3c810114fdeb22452084e77f3f847c6dbaafe16", + "c2aef5e0ca43e82641565b8cb943aa8ba53550caef793b6532fafad94b816082f0113a3ea2f63608ab40437ecc0f0229cb8fa224dcf1c478a67d9b64162b92d1", + "15f534efff7105cd1c254d074e27d5898b89313b7d366dc2d7d87113fa7d53aae13f6dba487ad8103d5e854c91fdb6e1e74b2ef6d1431769c30767dde067a35c", + "89acbca0b169897a0a2714c2df8c95b5b79cb69390142b7d6018bb3e3076b099b79a964152a9d912b1b86412b7e372e9cecad7f25d4cbab8a317be36492a67d7", + "e3c0739190ed849c9c962fd9dbb55e207e624fcac1eb417691515499eea8d8267b7e8f1287a63633af5011fde8c4ddf55bfdf722edf88831414f2cfaed59cb9a", + "8d6cf87c08380d2d1506eee46fd4222d21d8c04e585fbfd08269c98f702833a156326a0724656400ee09351d57b440175e2a5de93cc5f80db6daf83576cf75fa", + "da24bede383666d563eeed37f6319baf20d5c75d1635a6ba5ef4cfa1ac95487e96f8c08af600aab87c986ebad49fc70a58b4890b9c876e091016daf49e1d322e", + "f9d1d1b1e87ea7ae753a029750cc1cf3d0157d41805e245c5617bb934e732f0ae3180b78e05bfe76c7c3051e3e3ac78b9b50c05142657e1e03215d6ec7bfd0fc", + "11b7bc1668032048aa43343de476395e814bbbc223678db951a1b03a021efac948cfbe215f97fe9a72a2f6bc039e3956bfa417c1a9f10d6d7ba5d3d32ff323e5", + "b8d9000e4fc2b066edb91afee8e7eb0f24e3a201db8b6793c0608581e628ed0bcc4e5aa6787992a4bcc44e288093e63ee83abd0bc3ec6d0934a674a4da13838a", + "ce325e294f9b6719d6b61278276ae06a2564c03bb0b783fafe785bdf89c7d5acd83e78756d301b445699024eaeb77b54d477336ec2a4f332f2b3f88765ddb0c3", + "29acc30e9603ae2fccf90bf97e6cc463ebe28c1b2f9b4b765e70537c25c702a29dcbfbf14c99c54345ba2b51f17b77b5f15db92bbad8fa95c471f5d070a137cc", + "3379cbaae562a87b4c0425550ffdd6bfe1203f0d666cc7ea095be407a5dfe61ee91441cd5154b3e53b4f5fb31ad4c7a9ad5c7af4ae679aa51a54003a54ca6b2d", + "3095a349d245708c7cf550118703d7302c27b60af5d4e67fc978f8a4e60953c7a04f92fcf41aee64321ccb707a895851552b1e37b00bc5e6b72fa5bcef9e3fff", + "07262d738b09321f4dbccec4bb26f48cb0f0ed246ce0b31b9a6e7bc683049f1f3e5545f28ce932dd985c5ab0f43bd6de0770560af329065ed2e49d34624c2cbb", + "b6405eca8ee3316c87061cc6ec18dba53e6c250c63ba1f3bae9e55dd3498036af08cd272aa24d713c6020d77ab2f3919af1a32f307420618ab97e73953994fb4", + "7ee682f63148ee45f6e5315da81e5c6e557c2c34641fc509c7a5701088c38a74756168e2cd8d351e88fd1a451f360a01f5b2580f9b5a2e8cfc138f3dd59a3ffc", + "1d263c179d6b268f6fa016f3a4f29e943891125ed8593c81256059f5a7b44af2dcb2030d175c00e62ecaf7ee96682aa07ab20a611024a28532b1c25b86657902", + "106d132cbdb4cd2597812846e2bc1bf732fec5f0a5f65dbb39ec4e6dc64ab2ce6d24630d0f15a805c3540025d84afa98e36703c3dbee713e72dde8465bc1be7e", + "0e79968226650667a8d862ea8da4891af56a4e3a8b6d1750e394f0dea76d640d85077bcec2cc86886e506751b4f6a5838f7f0b5fef765d9dc90dcdcbaf079f08", + "521156a82ab0c4e566e5844d5e31ad9aaf144bbd5a464fdca34dbd5717e8ff711d3ffebbfa085d67fe996a34f6d3e4e60b1396bf4b1610c263bdbb834d560816", + "1aba88befc55bc25efbce02db8b9933e46f57661baeabeb21cc2574d2a518a3cba5dc5a38e49713440b25f9c744e75f6b85c9d8f4681f676160f6105357b8406", + "5a9949fcb2c473cda968ac1b5d08566dc2d816d960f57e63b898fa701cf8ebd3f59b124d95bfbbedc5f1cf0e17d5eaed0c02c50b69d8a402cabcca4433b51fd4", + "b0cead09807c672af2eb2b0f06dde46cf5370e15a4096b1a7d7cbb36ec31c205fbefca00b7a4162fa89fb4fb3eb78d79770c23f44e7206664ce3cd931c291e5d", + "bb6664931ec97044e45b2ae420ae1c551a8874bc937d08e969399c3964ebdba8346cdd5d09caafe4c28ba7ec788191ceca65ddd6f95f18583e040d0f30d0364d", + "65bc770a5faa3792369803683e844b0be7ee96f29f6d6a35568006bd5590f9a4ef639b7a8061c7b0424b66b60ac34af3119905f33a9d8c3ae18382ca9b689900", + "ea9b4dca333336aaf839a45c6eaa48b8cb4c7ddabffea4f643d6357ea6628a480a5b45f2b052c1b07d1fedca918b6f1139d80f74c24510dcbaa4be70eacc1b06", + "e6342fb4a780ad975d0e24bce149989b91d360557e87994f6b457b895575cc02d0c15bad3ce7577f4c63927ff13f3e381ff7e72bdbe745324844a9d27e3f1c01", + "3e209c9b33e8e461178ab46b1c64b49a07fb745f1c8bc95fbfb94c6b87c69516651b264ef980937fad41238b91ddc011a5dd777c7efd4494b4b6ecd3a9c22ac0", + "fd6a3d5b1875d80486d6e69694a56dbb04a99a4d051f15db2689776ba1c4882e6d462a603b7015dc9f4b7450f05394303b8652cfb404a266962c41bae6e18a94", + "951e27517e6bad9e4195fc8671dee3e7e9be69cee1422cb9fecfce0dba875f7b310b93ee3a3d558f941f635f668ff832d2c1d033c5e2f0997e4c66f147344e02", + "8eba2f874f1ae84041903c7c4253c82292530fc8509550bfdc34c95c7e2889d5650b0ad8cb988e5c4894cb87fbfbb19612ea93ccc4c5cad17158b9763464b492", + "16f712eaa1b7c6354719a8e7dbdfaf55e4063a4d277d947550019b38dfb564830911057d50506136e2394c3b28945cc964967d54e3000c2181626cfb9b73efd2", + "c39639e7d5c7fb8cdd0fd3e6a52096039437122f21c78f1679cea9d78a734c56ecbeb28654b4f18e342c331f6f7229ec4b4bc281b2d80a6eb50043f31796c88c", + "72d081af99f8a173dcc9a0ac4eb3557405639a29084b54a40172912a2f8a395129d5536f0918e902f9e8fa6000995f4168ddc5f893011be6a0dbc9b8a1a3f5bb", + "c11aa81e5efd24d5fc27ee586cfd8847fbb0e27601ccece5ecca0198e3c7765393bb74457c7e7a27eb9170350e1fb53857177506be3e762cc0f14d8c3afe9077", + "c28f2150b452e6c0c424bcde6f8d72007f9310fed7f2f87de0dbb64f4479d6c1441ba66f44b2accee61609177ed340128b407ecec7c64bbe50d63d22d8627727", + "f63d88122877ec30b8c8b00d22e89000a966426112bd44166e2f525b769ccbe9b286d437a0129130dde1a86c43e04bedb594e671d98283afe64ce331de9828fd", + "348b0532880b88a6614a8d7408c3f913357fbb60e995c60205be9139e74998aede7f4581e42f6b52698f7fa1219708c14498067fd1e09502de83a77dd281150c", + "5133dc8bef725359dff59792d85eaf75b7e1dcd1978b01c35b1b85fcebc63388ad99a17b6346a217dc1a9622ebd122ecf6913c4d31a6b52a695b86af00d741a0", + "2753c4c0e98ecad806e88780ec27fccd0f5c1ab547f9e4bf1659d192c23aa2cc971b58b6802580baef8adc3b776ef7086b2545c2987f348ee3719cdef258c403", + "b1663573ce4b9d8caefc865012f3e39714b9898a5da6ce17c25a6a47931a9ddb9bbe98adaa553beed436e89578455416c2a52a525cf2862b8d1d49a2531b7391", + "64f58bd6bfc856f5e873b2a2956ea0eda0d6db0da39c8c7fc67c9f9feefcff3072cdf9e6ea37f69a44f0c61aa0da3693c2db5b54960c0281a088151db42b11e8", + "0764c7be28125d9065c4b98a69d60aede703547c66a12e17e1c618994132f5ef82482c1e3fe3146cc65376cc109f0138ed9a80e49f1f3c7d610d2f2432f20605", + "f748784398a2ff03ebeb07e155e66116a839741a336e32da71ec696001f0ad1b25cd48c69cfca7265eca1dd71904a0ce748ac4124f3571076dfa7116a9cf00e9", + "3f0dbc0186bceb6b785ba78d2a2a013c910be157bdaffae81bb6663b1a73722f7f1228795f3ecada87cf6ef0078474af73f31eca0cc200ed975b6893f761cb6d", + "d4762cd4599876ca75b2b8fe249944dbd27ace741fdab93616cbc6e425460feb51d4e7adcc38180e7fc47c89024a7f56191adb878dfde4ead62223f5a2610efe", + "cd36b3d5b4c91b90fcbba79513cfee1907d8645a162afd0cd4cf4192d4a5f4c892183a8eacdb2b6b6a9d9aa8c11ac1b261b380dbee24ca468f1bfd043c58eefe", + "98593452281661a53c48a9d8cd790826c1a1ce567738053d0bee4a91a3d5bd92eefdbabebe3204f2031ca5f781bda99ef5d8ae56e5b04a9e1ecd21b0eb05d3e1", + "771f57dd2775ccdab55921d3e8e30ccf484d61fe1c1b9c2ae819d0fb2a12fab9be70c4a7a138da84e8280435daade5bbe66af0836a154f817fb17f3397e725a3", + "c60897c6f828e21f16fbb5f15b323f87b6c8955eabf1d38061f707f608abdd993fac3070633e286cf8339ce295dd352df4b4b40b2f29da1dd50b3a05d079e6bb", + "8210cd2c2d3b135c2cf07fa0d1433cd771f325d075c6469d9c7f1ba0943cd4ab09808cabf4acb9ce5bb88b498929b4b847f681ad2c490d042db2aec94214b06b", + "1d4edfffd8fd80f7e4107840fa3aa31e32598491e4af7013c197a65b7f36dd3ac4b478456111cd4309d9243510782fa31b7c4c95fa951520d020eb7e5c36e4ef", + "af8e6e91fab46ce4873e1a50a8ef448cc29121f7f74deef34a71ef89cc00d9274bc6c2454bbb3230d8b2ec94c62b1dec85f3593bfa30ea6f7a44d7c09465a253", + "29fd384ed4906f2d13aa9fe7af905990938bed807f1832454a372ab412eea1f5625a1fcc9ac8343b7c67c5aba6e0b1cc4644654913692c6b39eb9187ceacd3ec", + "a268c7885d9874a51c44dffed8ea53e94f78456e0b2ed99ff5a3924760813826d960a15edbedbb5de5226ba4b074e71b05c55b9756bb79e55c02754c2c7b6c8a", + "0cf8545488d56a86817cd7ecb10f7116b7ea530a45b6ea497b6c72c997e09e3d0da8698f46bb006fc977c2cd3d1177463ac9057fdd1662c85d0c126443c10473", + "b39614268fdd8781515e2cfebf89b4d5402bab10c226e6344e6b9ae000fb0d6c79cb2f3ec80e80eaeb1980d2f8698916bd2e9f747236655116649cd3ca23a837", + "74bef092fc6f1e5dba3663a3fb003b2a5ba257496536d99f62b9d73f8f9eb3ce9ff3eec709eb883655ec9eb896b9128f2afc89cf7d1ab58a72f4a3bf034d2b4a", + "3a988d38d75611f3ef38b8774980b33e573b6c57bee0469ba5eed9b44f29945e7347967fba2c162e1c3be7f310f2f75ee2381e7bfd6b3f0baea8d95dfb1dafb1", + "58aedfce6f67ddc85a28c992f1c0bd0969f041e66f1ee88020a125cbfcfebcd61709c9c4eba192c15e69f020d462486019fa8dea0cd7a42921a19d2fe546d43d", + "9347bd291473e6b4e368437b8e561e065f649a6d8ada479ad09b1999a8f26b91cf6120fd3bfe014e83f23acfa4c0ad7b3712b2c3c0733270663112ccd9285cd9", + "b32163e7c5dbb5f51fdc11d2eac875efbbcb7e7699090a7e7ff8a8d50795af5d74d9ff98543ef8cdf89ac13d0485278756e0ef00c817745661e1d59fe38e7537", + "1085d78307b1c4b008c57a2e7e5b234658a0a82e4ff1e4aaac72b312fda0fe27d233bc5b10e9cc17fdc7697b540c7d95eb215a19a1a0e20e1abfa126efd568c7", + "4e5c734c7dde011d83eac2b7347b373594f92d7091b9ca34cb9c6f39bdf5a8d2f134379e16d822f6522170ccf2ddd55c84b9e6c64fc927ac4cf8dfb2a17701f2", + "695d83bd990a1117b3d0ce06cc888027d12a054c2677fd82f0d4fbfc93575523e7991a5e35a3752e9b70ce62992e268a877744cdd435f5f130869c9a2074b338", + "a6213743568e3b3158b9184301f3690847554c68457cb40fc9a4b8cfd8d4a118c301a07737aeda0f929c68913c5f51c80394f53bff1c3e83b2e40ca97eba9e15", + "d444bfa2362a96df213d070e33fa841f51334e4e76866b8139e8af3bb3398be2dfaddcbc56b9146de9f68118dc5829e74b0c28d7711907b121f9161cb92b69a9", + "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461", +} + +var hashes2X = []string{ + "64", + "f457", + "e8c045", + "a74c6d0d", + "eb02ae482a", + "be65b981275e", + "8540ccd083a455", + "074a02fa58d7c7c0", + "da6da05e10db3022b6", + "542a5aae2f28f2c3b68c", + "ca3af2afc4afe891da78b1", + "e0f66b8dcebf4edc85f12c85", + "744224d383733b3fa2c53bfcf5", + "b09b653e85b72ef5cdf8fcfa95f3", + "dd51877f31f1cf7b9f68bbb09064a3", + "f5ebf68e7ebed6ad445ffc0c47e82650", + "ebdcfe03bcb7e21a9091202c5938c0a1bb", + "860fa5a72ff92efafc48a89df1632a4e2809", + "0d6d49daa26ae2818041108df3ce0a4db48c8d", + "e5d7e1bc5715f5ae991e4043e39533af5d53e47f", + "5232028a43b9d4dfa7f37439b49495926481ab8a29", + "c118803c922f9ae2397fb676a2ab7603dd9c29c21fe4", + "2af924f48b9bd7076bfd68794bba6402e2a7ae048de3ea", + "61255ac38231087c79ea1a0fa14538c26be1c851b6f318c0", + "f9712b8e42f0532162822f142cb946c40369f2f0e77b6b186e", + "76da0b89558df66f9b1e66a61d1e795b178ce77a359087793ff2", + "9036fd1eb32061bdecebc4a32aa524b343b8098a16768ee774d93c", + "f4ce5a05934e125d159678bea521f585574bcf9572629f155f63efcc", + "5e1c0d9fae56393445d3024d6b82692d1339f7b5936f68b062c691d3bf", + "538e35f3e11111d7c4bab69f83b30ade4f67addf1f45cdd2ac74bf299509", + "17572c4dcbb17faf8785f3bba9f6903895394352eae79b01ebd758377694cc", + "29f6bb55de7f8868e053176c878c9fe6c2055c4c5413b51ab0386c277fdbac75", + "bad026c8b2bd3d294907f2280a7145253ec2117d76e3800357be6d431b16366e41", + "386b7cb6e0fd4b27783125cbe80065af8eb9981fafc3ed18d8120863d972fa7427d9", + "06e8e6e26e756fff0b83b226dce974c21f970e44fb5b3e5bbada6e4b12f81cca666f48", + "2f9bd300244f5bc093ba6dcdb4a89fa29da22b1de9d2c9762af919b5fedf6998fbda305b", + "cf6bdcc46d788074511f9e8f0a4b86704365b2d3f98340b8db53920c385b959a38c8869ae7", + "1171e603e5cdeb4cda8fd7890222dd8390ede87b6f3284cac0f0d832d8250c9200715af7913d", + "bda7b2ad5d02bd35ffb009bdd72b7d7bc9c28b3a32f32b0ba31d6cbd3ee87c60b7b98c03404621", + "2001455324e748503aa08eff2fb2e52ae0170e81a6e9368ada054a36ca340fb779393fb045ac72b3", + "45f0761aefafbf87a68f9f1f801148d9bba52616ad5ee8e8ac9207e9846a782f487d5cca8b20355a18", + "3a7e05708be62f087f17b41ac9f20e4ef8115c5ab6d08e84d46af8c273fb46d3ce1aabebae5eea14e018", + "ea318da9d042ca337ccdfb2bee3e96ecb8f907876c8d143e8e44569178353c2e593e4a82c265931ba1dd79", + "e0f7c08f5bd712f87094b04528fadb283d83c9ceb82a3e39ec31c19a42a1a1c3bee5613b5640abe069b0d690", + "d35e63fb1f3f52ab8f7c6cd7c8247e9799042e53922fbaea808ab979fa0c096588cfea3009181d2f93002dfc11", + "b8b0ab69e3ae55a8699eb481dd665b6a2424c89bc6b7cca02d15fdf1b9854139cab49d34de498b50b2c7e8b910cf", + "fb65e3222a2950eae1701d4cdd4736266f65bf2c0d2e77968996eadb60ef74fb786f6234973a2524bdfe32d100aa0e", + "f28b4bb3a2e2c4d5c01a23ff134558559a2d3d704b75402983ee4e0f71d273ae056842c4153b18ee5c47e2bfa54313d4", + "7bb78794e58a53c3e4b1aeb161e756af051583d14e0a5a3205e094b7c9a8cf62d098fa9ea1db12f330a51ab9852c17f983", + "a879a8ebae4d0987789bcc58ec3448e35ba1fa1ee58c668d8295aba4eaeaf2762b053a677e25404f635a53037996974d418a", + "695865b353ec701ecc1cb38f3154489eed0d39829fc192bb68db286d20fa0a64235cde5639137819f7e99f86bd89afcef84a0f", + "a6ec25f369f71176952fb9b33305dc768589a6070463ee4c35996e1ced4964a865a5c3dc8f0d809eab71366450de702318e4834d", + "604749f7bfadb069a036409ffac5ba291fa05be8cba2f141554132f56d9bcb88d1ce12f2004cd3ade1aa66a26e6ef64e327514096d", + "daf9fa7dc2464a899533594e7916fc9bc585bd29dd60c930f3bfa78bc47f6c8439448043a45119fc9228c15bce5fd24f46baf9de736b", + "943ea5647a8666763084da6a6f15dcf0e8dc24f27fd0d9194805d25180fe3a6d98f4b2b5e0d6a04e9b41869817030f16ae975dd41fc35c", + "af4f73cbfc093760dfeb52d57ef45207bbd1a515f5523404e5d95a73c237d97ae65bd195b472de6d514c2c448b12fafc282166da132258e9", + "605f4ed72ed7f5046a342fe4cf6808100d4632e610d59f7ebb016e367d0ff0a95cf45b02c727ba71f147e95212f52046804d376c918cadd260", + "3750d8ab0a6b13f78e51d321dfd1aa801680e958de45b7b977d05732ee39f856b27cb2bcce8fbf3db6666d35e21244c2881fdcc27fbfea6b1672", + "8f1b929e80ab752b58abe9731b7b34eb61369536995abef1c0980d93903c1880da3637d367456895f0cb4769d6de3a979e38ed6f5f6ac4d48e9b32", + "d8469b7aa538b36cdc711a591d60dafecca22bd421973a70e2deef72f69d8014a6f0064eabfbebf5383cbb90f452c6e113d2110e4b1092c54a38b857", + "7d1f1ad2029f4880e1898af8289c23bc933a40863cc4ab697fead79c58b6b8e25b68cf5324579b0fe879fe7a12e6d03907f0140dfe7b29d33d6109ecf1", + "87a77aca6d551642288a0dff66078225ae39d288801607429d6725ca949eed7a6f199dd8a65523b4ee7cfa4187400e96597bfffc3e38ade0ae0ab88536a9", + "e101f43179d8e8546e5ce6a96d7556b7e6b9d4a7d00e7aade5579d085d527ce34a9329551ebcaf6ba946949bbe38e30a62ae344c1950b4bde55306b3bac432", + "4324561d76c370ef35ac36a4adf8f3773a50d86504bd284f71f7ce9e2bc4c1f1d34a7fb2d67561d101955d448b67577eb30dfee96a95c7f921ef53e20be8bc44", + "78f0ed6e220b3da3cc9381563b2f72c8dc830cb0f39a48c6ae479a6a78dcfa94002631dec467e9e9b47cc8f0887eb680e340aec3ec009d4a33d241533c76c8ca8c", + "9f6589c31a472e0a736f4eb22b6c70a9d332cc15304ccb66a6b97cd051b6ed82f8990e1d9bee2e4bb1c3c45e550ae0e7b96e93ae23f2fb8f63b309131e72b36cba6a", + "c138077ee4ed3d7ffa85ba851dfdf6e9843fc1dc00889d117237bfaad9aa757192f73556b959f98e6d24886ce48869f2a01a48c371785f12b6484eb2078f08c22066e1", + "f83e7c9e0954a500576ea1fc90a3db2cbd7994eaef647dab5b34e88ab9dc0b47addbc807b21c8e6dd3d0bd357f008471d4f3e0abb18450e1d4919e03a34545b9643f870e", + "3277a11f2628544fc66f50428f1ad56bcba6ee36ba2ca6ecdf7e255effc0c30235c039d13e01f04cf1efe95b5c2033ab72adda30994b62f2851d17c9920eadca9a251752dc", + "c2a834281a06fe7b730d3a03f90761daf02714c066e33fc07e1f59ac801ec2f4433486b5a2da8faa51a0cf3c34e29b2960cd0013378938dbd47c3a3d12d70db01d7d06c3e91e", + "47680182924a51cabe142a6175c9253e8ba7ea579ece8d9bcb78b1e9ca00db844fa08abcf41702bd758ee2c608d9612fed50e85854469cb4ef3038acf1e35b6ba4390561d8ae82", + "cec45830cd71869e83b109a99a3cd7d935f83a95de7c582f3adbd34e4938fa2f3f922f52f14f169c38cc6618d3f306a8a4d607b345b8a9c48017136fbf825aecf7b620e85f837fae", + "46fb53c70ab105079d5d78dc60eaa30d938f26e4d0b9df122e21ec85deda94744c1daf8038b8a6652d1ff3e7e15376f5abd30e564784a999f665078340d66b0e939e0c2ef03f9c08bb", + "7b0dcb52791a170cc52f2e8b95d8956f325c3751d3ef3b2b83b41d82d4496b46228a750d02b71a96012e56b0720949ca77dc68be9b1ef1ad6d6a5ceb86bf565cb972279039e209dddcdc", + "7153fd43e6b05f5e1a4401e0fef954a737ed142ec2f60bc4daeef9ce73ea1b40a0fcaf1a1e03a3513f930dd5335723632f59f7297fe3a98b68e125eadf478eb045ed9fc4ee566d13f537f5", + "c7f569c79c801dab50e9d9ca6542f25774b3841e49c83efe0b89109f569509ce7887bc0d2b57b50320eb81fab9017f16c4c870e59edb6c26620d93748500231d70a36f48a7c60747ca2d5986", + "0a81e0c547648595adca65623ce783411aac7f7d30c3ad269efafab288e7186f6895261972f5137877669c550f34f5128850ebb50e1884814ea1055ee29a866afd04b2087abed02d9592573428", + "6a7b6769e1f1c95314b0c7fe77013567891bd23416374f23e4f43e27bc4c55cfada13b53b1581948e07fb96a50676baa2756db0988077b0f27d36ac088e0ff0fe72eda1e8eb4b8facff3218d9af0", + "a399474595cb1ccab6107f18e80f03b1707745c7bf769fc9f260094dc9f8bc6fe09271cb0b131ebb2acd073de4a6521c8368e664278be86be216d1622393f23435fae4fbc6a2e7c961282a777c2d75", + "4f0fc590b2755a515ae6b46e9628092369d9c8e589e3239320639aa8f7aa44f8111c7c4b3fdbe6e55e036fbf5ebc9c0aa87a4e66851c11e86f6cbf0bd9eb1c98a378c7a7d3af900f55ee108b59bc9e5c", + "ed96a046f08dd675107331d267379c6fce3c352a9f8d7b243008a74cb4e9410836afaabe871dab6038ca94ce5f6d41fa922ce08aba58169f94cfc86d9f688f396abd24c11a6a9b0830572105a477c33e92", + "379955f539abf0eb2972ee99ed9546c4bbee363403991833005dc27904c271ef22a799bc32cb39f08d2e4ba6717d55153feb692d7c5efae70890bf29d96df02333c7b05ccc314e4835b018fec9141a82c745", + "e16cc8d41b96547ede0d0cf4d908c5fa393399daa4a9696e76a4c1f6a2a9fef70f17fb53551a8145ed88f18db8fe780a079d94732437023f7c1d1849ef69ad536a76204239e8ba5d97e507c36c7d042f87fe0e", + "a81de50750ece3f84536728f227208bf01ec5b7721579d007de72c88ee20663318332efe5bc7c09ad1fa8342be51f0609046ccf760a7957a7d8dc88941adb93666a4521ebe76618e5ddc2dd3261493d400b50073", + "b72c5fb7c7f60d243928fa41a2d711157b96aef290185c64b4de3dcfa3d644da67a8f37c2ac55caad79ec695a473e8b481f658c497edb8a191526592b11a412282d2a4010c90ef4647bd6ce745ebc9244a71d4876b", + "9550703877079c90e200e830f277b605624954c549e729c359ee01ee2b07741ecc4255cb37f96682dafcdbaade1063e2c5ccbd1918fb669926a67744101fb6de3ac016be4c74165a1e5a696b704ba2ebf4a953d44b95", + "a17eb44d4de502dc04a80d5a5e9507d17f27c96467f24c79b06bc98a4c410741d4ac2db98ec02c2a976d788531f1a4451b6c6204cef6dae1b6ebbcd0bde23e6fffb02754043c8fd3c783d90a670b16879ce68b5554fe1c", + "41d3ea1eaba5be4a206732dbb5b70b79b66a6e5908795ad4fb7cf9e67efb13f06fef8f90acb080ce082aadec6a1b543af759ab63fa6f1d3941186482b0c2b312f1151ea8386253a13ed3708093279b8eb04185636488b226", + "5e7cdd8373dc42a243c96013cd29df9283b5f28bb50453a903c85e2ce57f35861bf93f03029072b70dac0804e7d51fd0c578c8d9fa619f1e9ce3d8044f65d55634dba611280c1d5cfb59c836a595c803124f696b07ddfac718", + "26a14c4aa168907cb5de0d12a82e1373a128fb21f2ed11feba108b1bebce934ad63ed89f4ed7ea5e0bc8846e4fc10142f82de0bebd39d68f7874f615c3a9c896bab34190e85df05aaa316e14820b5e478d838fa89dfc94a7fc1e", + "0211dfc3c35881adc170e4ba6daab1b702dff88933db9a6829a76b8f4a7c2a6d658117132a974f0a0b3a38ceea1efc2488da21905345909e1d859921dc2b5054f09bce8eeb91fa2fc6d048ce00b9cd655e6aafbdaa3a2f19270a16", + "ddf015b01b68c4f5f72c3145d54049867d99ee6bef24282abf0eecdb506e295bacf8f23ffa65a4cd891f76a046b9dd82cae43a8d01e18a8dff3b50aeb92672be69d7c087ec1fa2d3b2a39196ea5b49b7baede37a586fea71aded587f", + "6ee721f71ca4dd5c9ce7873c5c04c6ce76a2c824b984251c15535afc96adc9a4d48ca314bfeb6b8ee65092f14cf2a7ca9614e1dcf24c2a7f0f0c11207d3d8aed4af92873b56e8b9ba2fbd659c3f4ca90fa24f113f74a37181bf0fdf758", + "689bd150e65ac123612524f720f54def78c095eaab8a87b8bcc72b443408e3227f5c8e2bd5af9bcac684d497bc3e41b7a022c28fb5458b95e8dfa2e8caccde0492936ff1902476bb7b4ef2125b19aca2cd3384d922d9f36dddbcd96ae0d6", + "3a3c0ef066fa4390ec76ad6be1dc9c31ddf45fef43fbfa1f49b439caa2eb9f3042253a9853e96a9cf86b4f873785a5d2c5d3b05f6501bc876e09031188e05f48937bf3c9b667d14800db62437590b84ce96aa70bb5141ee2ea41b55a6fd944", + "741ce384e5e0edaebb136701ce38b3d33215415197758ae81235307a4115777d4dab23891db530c6d28f63a957428391421f742789a0e04c99c828373d9903b64dd57f26b3a38b67df829ae243feef731ead0abfca049924667fdec49d40f665", + "a513f450d66cd5a48a115aee862c65b26e836f35a5eb6894a80519e2cd96cc4cad8ed7eb922b4fc9bbc55c973089d627b1da9c3a95f6c019ef1d47143cc545b15e4244424be28199c51a5efc7234dcd94e72d229897c392af85f523c2633427825", + "71f1554d2d49bb7bd9e62e71fa049fb54a2c097032f61ebda669b3e1d4593962e47fc62a0ab5d85706aebd6a2f9a192c88aa1ee2f6a46710cf4af6d3c25b7e68ad5c3db23ac009c8f13625ff85dc8e50a9a1b2682d3329330b973ec8cbb7bb73b2bd", + "167cc1067bc08a8d2c1a0c10041ebe1fc327b37043f6bd8f1c63569e9d36ded58519e66b162f34b6d8f1107ef1e3de199d97b36b44141a1fc4f49b883f40507ff11f909a017869dc8a2357fc7336ae68703d25f75710b0ff5f9765321c0fa53a51675c", + "cb859b35dc70e264efaad2a809fea1e71cd4a3f924be3b5a13f8687a1166b538c40b2ad51d5c3e47b0de482497382673140f547068ff0b3b0fb7501209e1bf36082509ae85f60bb98fd02ac50d883a1a8daa704952d83c1f6da60c9624bc7c99912930bf", + "afb1f0c6b7125b04fa2578dd40f60cb411b35ebc7026c702e25b3f0ae3d4695d44cfdf37cb755691dd9c365edadf21ee44245620e6a24d4c2497135b37cd7ac67e3bd0aaee9f63f107746f9b88859ea902bc7d6895406aa2161f480cad56327d0a5bba2836", + "13e9c0522587460d90c7cb354604de8f1bf850e75b4b176bda92862d35ec810861f7d5e7ff6ba9302f2c2c8642ff8b7776a2f53665790f570fcef3cac069a90d50db42227331c4affb33d6c040d75b9aeafc9086eb83ced38bb02c759e95ba08c92b17031288", + "0549812d62d3ed497307673a4806a21060987a4dbbf43d352b9b170a29240954cf04bc3e1e250476e6800b79e843a8bd8253b7d743de01ab336e978d4bea384eaff700ce020691647411b10a60acacb6f8837fb08ad666b8dcc9eaa87ccb42aef6914a3f3bc30a", + "3a263efbe1f2d463f20526e1d0fd735035fd3f808925f058b32c4d8788aeeab9b8ce233b3c34894731cd73361f465bd350395aebcabd2fb63010298ca025d849c1fa3cd573309b74d7f824bbfe383f09db24bcc565f636b877333206a6ad70815c3bef5574c5fc1c", + "3c6a7d8a84ef7e3eaa812fc1eb8e85105467230d2c9e4562edbfd808f4d1ac15d16b786cc6a02959c2bc17149c2ce74c6f85ee5ef22a8a96b9be1f197cffd214c1ab02a06a9227f37cd432579f8c28ff2b5ac91cca8ffe6240932739d56788c354e92c591e1dd76499", + "b571859294b02af17541a0b5e899a5f67d6f5e36d38255bc417486e69240db56b09cf2607fbf4f95d085a779358a8a8b41f36503438c1860c8f361ce0f2783a08b21bd7232b50ca6d35428335272a5c05b436b2631d8d5c84d60e8040083768ce56a250727fb0579dd5c", + "98ee1b7269d2a0dd490ca38d447279870ea55326571a1b430adbb2cf65c492131136f504145df3ab113a13abfb72c33663266b8bc9c458db4bf5d7ef03e1d3b8a99d5de0c024be8fabc8dc4f5dac82a0342d8ed65c329e7018d6997e69e29a01350516c86beaf153da65ac", + "41c5c95f088df320d35269e5bf86d10248f17aec6776f0fe653f1c356aae409788c938befeb67c86d1c8870e8099ca0ce61a80fbb5a6654c44529368f70fc9b9c2f912f5092047d0ffc339577d24142300e34948e086f62e23ecaca410d24f8a36b5c8c5a80e0926bc8aa16a", + "9f93c41f533b2a82a4df893c78faaaa793c1506974ba2a604cd33101713ca4adfd30819ffd8403402b8d40aff78106f3357f3e2c24312c0d3603a17184d7b999fc9908d14d50192aebabd90d05073da7af4be37dd3d81c90acc80e8333df546f17ab6874f1ec204392d1c0571e", + "3da5207245ac270a915fc91cdb314e5a2577c4f8e269c4e701f0d7493ba716de79935918b917a2bd5db98050dbd1eb3894b65fac5abf13e075abebc011e651c03cafb6127147771a5c8418223e1548137a89206635c26ca9c235ccc108dc25cf846e4732444bd0c2782b197b262b", + "96011af3965bb941dc8f749932ea484eccb9ba94e34b39f24c1e80410f96ce1d4f6e0aa5be606def4f54301e930493d4b55d484d93ab9dd4dc2c9cfb79345363af31ad42f4bd1aa6c77b8afc9f0d551bef7570b13b927afe3e7ac4de7603a0876d5edb1ad9be05e9ee8b53941e8f59", + "51dbbf2a7ca224e524e3454fe82ddc901fafd2120fa8603bc343f129484e9600f688586e040566de0351d1693829045232d04ff31aa6b80125c763faab2a9b233313d931903dcfaba490538b06e4688a35886dc24cdd32a13875e6acf45454a8eb8a315ab95e608ad8b6a49aef0e299a", + "5a6a422529e22104681e8b18d64bc0463a45df19ae2633751c7aae412c250f8fb2cd5e1270d3d0cf009c8aa69688ccd4e2b6536f5747a5bc479b20c135bf4e89d33a26118705a614c6be7ecfe766932471ad4ba01c4f045b1abb5070f90ec78439a27a1788db9327d1c32f939e5fb1d5ba", + "5d26c983642093cb12ff0afabd87b7c56e211d01844ad6da3f623b9f20a0c968034299f2a65e6673530c5980a532beb831c7d0697d12760445986681076dfb6fae5f3a4d8f17a0db5008ce8619f566d2cfe4cf2a6d6f9c3664e3a48564a351c0b3c945c5ee24587521e4112c57e318be1b6a", + "52641dbc6e36be4d905d8d60311e303e8e859cc47901ce30d6f67f152343e3c4030e3a33463793c19effd81fb7c4d631a9479a7505a983a052b1e948ce093b30efa595fab3a00f4cef9a2f664ceeb07ec61719212d58966bca9f00a7d7a8cb4024cf6476bab7fbccee5fd4e7c3f5e2b2975aa2", + "a34ce135b37bf3db1c4aaa4878b4499bd2ee17b85578fcaf605d41e1826b45fdaa1b083d8235dc642787f11469a5493e36806504fe2a2063905e821475e2d5ee217057950370492f5024995e77b82aa51b4f5bd8ea24dc71e0a8a640b0592c0d80c24a726169cf0a10b40944747113d03b52708c", + "46b3cdf4946e15a5334fc3244d6680f5fc132afa67bf43bfade23d0c9e0ec64e7dab76faaeca1870c05f96b7d019411d8b0873d9fed04fa5057c039d5949a4d592827f619471359d6171691cfa8a5d7cb07ef2804f6ccad4821c56d4988bea7765f660f09ef87405f0a80bcf8559efa111f2a0b419", + "8b9fc21691477f11252fca050b121c5334eb4280aa11659e267297de1fec2b2294c7ccee9b59a149b9930b08bd320d3943130930a7d931b71d2f10234f4480c67f1de883d9894ada5ed5071660e221d78ae402f1f05af47761e13fec979f2671e3c63fb0ae7aa1327cf9b8313adab90794a52686bbc4", + "cd6598924ce847de7ff45b20ac940aa6292a8a99b56a74eddc24f2cfb45797188614a21d4e8867e23ff75afd7cd324248d58fcf1ddc73fbd115dfa8c09e62022fab540a59f87c989c12a86ded05130939f00cd2f3b512963dfe0289f0e54acad881c1027d2a0292138fdee902d67d9669c0ca1034a9456", + "594e1cd7337248704e691854af0fdb021067ddf7832b049ba7b684438c32b029eded2df2c89a6ff5f2f2c311522ae2dc6db5a815afc60637b15ec24ef9541f1550409db2a006da3affffe548a1eaee7bd114e9b805d0756c8e90c4dc33cb05226bc2b393b18d953f8730d4c7ae693159cdba758ad28964e2", + "1f0d292453f04406ada8be4c161b82e3cdd69099a8637659e0ee40b8f6da46005cfc6085db9804852decfbe9f7b4dda019a7112612895a144ed430a960c8b2f5458d3d56b7f427cee6358915aee7146278aed2a0296cdd929e4d21ef95a3adf8b7a6beba673cdccdbdcfb2474711732d972ad054b2dc64f38d", + "b65a72d4e1f9f9f75911cc46ad0806b9b18c87d105332a3fe183f45f063a746c892dc6c4b9181b1485b3e3a2cc3b453eba2d4c39d6905a774ed3fb755468beb190925ecd8e57ecb0d985125741650c6b6a1b2a3a50e93e3892c21d47ed5884eed83aa94e1602288f2f49fe286624de9d01fcb54433a0dc4ad70b", + "705ce0ffa469250782aff725248fc88fe98eb76659e8407edc1c4842c9867d61fe64fb86f74e980598b92bc213d06f337bd5654fc28643c7ba769a4c31563427543c00808b627a19c90d86c322f33566ce020121cc322229c3337943d46f68ef939d613dcef0077269f88151d6398b6b009abb763410b154ad76a3", + "7fa881ce87498440ab6af13854f0d851a7e0404de33896999a9b3292a5d2f5b3ad033530c558168fe5d2fdb9b89a2354c46cf32a0e612afc6c6485d789511bfef26800c74bf1a4cfbe30bda310d5f6029c3dccdedb6149e4971274e276dccfabd63bc4b9955e8303feb57f8a688db55ecb4b33d1f9fe1b3a8ba7ac32", + "23a98f71c01c0408ae16843dc03be7db0aeaf055f951709d4e0dfdf64fffbffaf900ee592ee10929648e56f6c1e9f5be5793f7df66453eb56502c7c56c0f0c88da77abc8fa371e434104627ef7c663c49f40998dbad63fa6c7aa4fac17ae138d8bbe081f9bd168cd33c1fbc92fa35ed687679f48a64b87db1fe5bae675", + "7b8970b6a33237e5a7bcb39272703edb92285c55842b30b9a48834b1b507cc02a6764739f2f7ee6ae02a7b715a1c455e59e8c77a1ae98abb10161853f1234d20da99016588cd8602d6b7ec7e177d4011edfa61e6b3766a3c6f8d6e9eac893c568903eb6e6aba9c4725774f6b4343b7acaa6c031593a36eef6c72806ff309", + "f7f4d328ba108b7b1de4443e889a985ed52f485f3ca4e0c246aa5526590cbed344e9f4fe53e4eea0e761c82324649206ca8c2b45152157d4115e68c818644b03b65bb47ad79f94d37cb03c1d953b74c2b8adfa0e1c418bda9c518ddcd7050e0f149044740a2b16479413b63fc13c36144f80c73687513dca761ba8642a8ae0", + "2d7dc80c19a1d12d5fe3963569547a5d1d3e821e6f06c5d5e2c09401f946c9f7e13cd019f2f9a878b62dd850453b6294b99ccaa068e542993524b0f63832d48e865be31e8ec1ee103c718340c904b32efb69170b67f038d50a3252794b1b4076c0620621ab3d91215d55ffea99f23d54e161a90d8d4902fda5931d9f6a27146a", + "77dff4c7ad30c954338c4b23639dae4b275086cbe654d401a2343528065e4c9f1f2eca22aa025d49ca823e76fdbb35df78b1e5075ff2c82b680bca385c6d57f7ea7d1030bb392527b25dd73e9eeff97bea397cf3b9dda0c817a9c870ed12c006cc054968c64000e0da874e9b7d7d621b0679866912243ea096c7b38a1344e98f74", + "83bed0d556798f2b419f7056e6d3ffada06e939b95a688d0ec8c6ac5ea45ab73a4cf01043e0a170766e21395f27ab4b78c435f5f0dfe6e93ab80df38610e41158429ddf20296f53a06a017723359fe22dc08b5da33f0800a4fe50118e8d7eab2f83a85cd764bf8a166903bd0e9dcfeeceba44ff4ca4439846458d31ea2bb564645d1", + "ea12cf5a113543e39504123036f15a5bafa9c555562469f99cd29996a4dfaaab2a34b00557ccf15f37fc0cc1b3be427e725f2cd952e50af7970dda9200cd5ce252b1f29c40067fea3027ed686190803b59d834179d1b8f5b55abe55ad174b2a1188f7753ec0ae2fc01316e7d498b68ee3598a0e9baaaa664a60f7fb4f90edbed494ad7", + "55266358332d8d9e68bd13432088beadf95833aab67a0eb3b10650414255f299e2670c3e1a5b2976159a46c72a7ce57d59b7be14c15798e09ed50fa312a431b0264d7a1396aa6168bde897e208ece53d2cfc83786113b1e6eac5e9bb98984abb6c8d64eebb991903254abc650c999bb9958a5d7937434b869bc940e21b9dc1cc8982f2ba", + "4d6104ded730aefe02873f4c741232c8234a6d66d85393aff57fbf56ba6347666988dfc4d58f3cc895a0da598822edeee4533d24ec0ee292fd5e1ad04898ffbc1ff4bef14dec220babcb0f28fffe32a6e2c28aaaac16442bf4feb02917d18bb3a415d84fa9358d5a9852688d846c92271911f934181c30f82434d915f93f155a1ffbf0b125", + "eb5f579a4c476af554aac11e5719d378549497e613b35a929d6f36bb8831d7a466aa76de9be24ebb55543f1c13924f64cfd648a5b3fa90387315c16174dbf1e9a183c196d9bb8f84af65f1f8212429aadc11ef2426d07d4716062b85c8d5d2dff8e21b9e62b7fa7dbd57d72633054b464fb28583a56ca13ccc5ddc74dae942492f31731e7046", + "ebddec3dcaf18063e45a76ebeac39af85a1adc2818881ccce48c106288f5988365cca2b4b1d7f037322da46840f42bebdcbc7193838d426e101087d8cea03aaff743d573eb4f4e9a71a2c884390769a6503874125d194bee8d46a3a0d5e4fcf28ff8465887d8e9df771d70157e75df3642b331d2778ceb32ceba868640171ab7a5d22eede1ee44", + "26d87ec70b57691e3bb359633d3ddba17f029d62cdfe977f5fd42274d79b444a32494d1c01e9f72d03cce78c806df96e93ea78da3a054209924ed765edc4d570f66168dc25ee3114e4017e387440349c8f0a94804761c3055f88e4fda2a49b860b1486a9609095f6250f268b6a4d1aecc03a505632ebf0b9dc22d0755a736faf7ad7000858b5864b", + "3880f5cc2d08fa70ef44b1f263fcf534d062a298c1bd5ee2eee8c3265806c4ce50b004f3a1fc1fa5b024aaac7f528c023c8181f67c6e1c357425dc4d573bd46b93a542afa3a19bdb140a2ce666e1a01f5c4d2dcd681fa9f5839b797813c394738d5ee4971386c12c7c117d17c7bec324b760aa30cda9ab2aa850284ba6fa97946f710f02449d1883c6", + "3317d2f452105dd3f4a96f9257af8285a80be58066b50f6f54bd633749b49f6ab9d57d45652d2ae852a2f6940cd5ec3159dd7f333358b12f502325df38843508faf7e246352d201280babd90b14fbf7722641c3601d0e458474439973c611bb5502fd0eb3078f87124ca7e1a016fcb6cfeff65f6a565985aca7122cfa8c5a11da0cb47797c5132333179", + "f2c5c955d0224e784a46b9125f8fef8a5e1271e145eb08bbbd07ca8e1cfc848cef14fa3b36221ac62006403dbb7f7d77958ccc54a8566c837858b809f3e310ace8ca682515bc655d2a397cab238a663b464d511f02dc5d033dad4cb5e0e519e94a54b62a3896e460ec70e5716b5921bf8396aa86a60123e6287e34570bb01bdc602e113670bf498af2ff10", + "180e275205691a83630cf4b0c7b80e6df8fad6ef1c23ba8013d2f09aef7abade1827f23af230de90676240b4b3b0673f8afdea0327330055041741f65560d90348de696d34ca80dfe8afae582fe4879d4594b80e9408fb53e800e01ca58552b905c365e7f1416e51c080f517d6bbd30e64ae1535d59decdc76c6624d737868f49f2f719da39ba1344d59eab9", + "c517a84e4631a7f65ace170d1e5c2fdb259841535d88da323e68c0883e6af7b041cfe05908815a5a9d1b14fa712c2c16fadcf1ca54d3aa954d411240df331b2aebdfb65aced84d0b8aace56ec0aa7c13ec7d75ca883b6bcf6db74c9e98463c484a8262684f29910373430651f90ecffe18b072170e61ee58de20e2a6ff67b3ab00fccbb80af943f20b56b98107", + "d1a56a5ee990e02b84b5862fde62f69ec07567be2d7ccb769a461c4989d11fdda6c945d942fb8b2da795ed97e43a5b7dbdde7f8fd2ff7154544336d5c50fb7380341e660d4898c7fbc39b2b782f28defac6873523c7c1de8e52c65e4395c686ba483c35a220b0416d46357a063fa4c33fa9c52d5c207a1304ae141c791e62ba6a7374ed922b8dd94079b72b69302", + "4720b88d6bfb1ab43958e26827730d852d9ec30173ebd0fe0d273edcece2e788558984cd9306fe5978086a5cb6d37975755d2a3daeb16f99a8a11544b8247a8b7ed5587afc5bea1daf85dcea5703c5905cf56ae7cc76408ccabb8fcc25cacc5ff456db3f62fa559c45b9c71505eb5073df1f10fc4c9060843f0cd68bbb4e8edfb48d0fd81d9c21e53b28a2aae4f7ba", + "f4639b511db9e092823d47d2947efacbaae0e5b912dec3b284d2350b9262f3a51796a0cd9f8bc5a65879d6578ec24a060e293100c2e12ad82d5b2a0e9d22965858030e7cdf2ab3562bfa8ac084c6e8237aa22f54b94c4e92d69f22169ced6c85a293f5e16bfc326153bf629cdd6393675c6627cd949cd367eef02e0f54779f4d5210197698e4754a5fe490a3a7521c1c", + "3d9e7a860a718565e3670c29079ce80e381969fea91017cfd5952e0d8a4a79bb08e2cd1e26161f30ee03a24891d1bfa8c212861b51618d07429fb48000ff87ef09c6fca526567777e9c076d58a642d5c521b1caa5fb0fb3a4b8982dc14a444732b72b239b8f01fc8ba8ee86b3013b5d3e98a92b2aeaecd4879fca5d5e9e0bd880dbfffa6f96f94f3998812aac6a714f331", + "4d9bf551d7fd531e7482e2ec875c0651b0bcc6caa738f7497befd11e67ae0e036c9d7ae4301cc3c7906f0d0e1ed4738753f414f9b3cd9b8a71176e325c4c74ce020680ecbfb146889597f5b40487e93f974cd866817fb9fb24c7c7c16177e6e120bfe349e83aa82ba40e59e917565788658a2b254f25cf99bc65070b3794cea2259eb10e42bb54852cba3110baa773dcd70c", + "b91f65ab5bc059bfa5b43b6ebae243b1c46826f3da061338b5af02b2da76bb5ebad2b426de3c3134a633499c7c36a120369727cb48a0c6cbab0acecdda137057159aa117a5d687c4286868f561a272e0c18966b2fec3e55d75abea818ce2d339e26adc005c2658493fe06271ad0cc33fcb25065e6a2a286af45a518aee5e2532f81ec9256f93ff2d0d41c9b9a2efdb1a2af899", + "736f6e387acb9acbee026a6080f8a9eb8dbb5d7c54ac7053ce75dd184b2cb7b942e22a3497419ddb3a04cf9e4eb9340a1a6f9474c06ee1dcfc8513979fee1fc4768087617fd424f4d65f54782c787a1d2de6efc81534343e855f20b3f3589027a5436201eee747d45b9b8375e4294d72ab6a52e04dfbb2914db92ee58f134b026527ed52d4f794459e02a43a17b0d51ea69bd7f3", + "9242d3eb31d26d923b99d66954cfade94f25a18912e6356810b63b971ae74bb53bc58b3c01424208ea1e0b1499936daea27e63d904f9ed65fdf69de40780a3027b2e89d94bdf214f585472613ce328f628f4f0d56217dfb53db5f7a07f54c8d71db16e27de7cdb8d23988837b49b65c12f1771d979e8b192c9f4a16b8d9fba917bcf74ce5a82aac2075608ba6c2d485fa59864b9de", + "5da68704f4b592d41f08aca08f62d85e2e2466e5f3be010315d11d113db674c4b98764a509a2f5aacc7ae72c9deff2bcc42810b47f64d429b35745b9efff0b18c58653461e968aaa3c2c7fc455bc5771a8f10cd184be831040df767201ab8d32cb9a58c89afbebecb524502c9b940c1b838f8361bbcde90d272715017f67609ea39b20fac985332d82daaa023999e3f8bfa5f3758bb8", + "71ea2af9c8ac2e5ae44a176662882e01027ca3cdb41ec2c6785606a07d7231cd4a2bded7155c2feef3d44d8fd42afa73265cef826f6e03aa761c5c51d5b1f129ddc27503ff50d9c2d748322df4b13dd5cdc7d46381528ab22b79b0049011e4d2e57fe2735e0d58d8d56e92c75dbeac8c76c4239d7f3f24fb56697593b3e4afa6671d5bbc96c079a1c154fe20212ade67b05d49ceaa7a84", + "1d133170582fa4bff59a21953ebbc01bc202d43cd79c083d1f5c02fa15a43a0f519e36acb710bdabac880f04bc003800641c2487930de9c03c0e0deb347fa815efca0a38c6c5de694db698743bc955581f6a945deec4ae988ef7cdf40498b77796ddea3fae0ea844891ab751c7ee20917c5a4af53cd4ebd82170078f41ada2795e6eea17593fa90cbf5290a1095e299fc7f507f360f187cd", + "5ec4ac45d48fc15c72471d795066bdf8e99a483d5fdd599511b9cdc408de7c0616491b73924d0266da34a495331a935c4b8884f57d7ad8cce4cbe586875aa52482215ed39d7626cce55d50349c7767981c8bd6890f132a196184247343566fc972b86fe3c5369d6a6519e9f07942f0522b77ad01c751dcf7defe31e471a0ec00963765dd8518144a3b8c3c978ad108056516a25dbe3092e73c", + "0d5e74b78290c689f2b3cfea45fc9b6a84c822639cd438a7f05c07c374adced42cdc12d2a9233a4ffe80307efc1ac13cb04300e165f8d90dd01c0ea955e7657332c6e86ad6b43e78ba4c13c675aed83192d8427866fb6484e6a3071b2369a46fba9005f31232da7ffec7952f831aaaddf63e225263531c2cf387f8cc14fa856c8795137142c3a52ffa69b8e30ebc88ce3bbc227597bcc8dddd89", + "a0fe36f983259921dc2fa7d89002b3066241d63bfc2448caf7e10522a35562be0bfedc3dce49cfce2e614a04d4c64cfc0ab898873a7fc26928dc1927c009d12f6f9b7a278205d3d0057604f4ac746f8b9287c3bc6b929832bf253b6586192ac43fdd29ba585dbd9059aab9c6ff6000a7867c67fec1457b733f6b620881166b8fed92bc8d84f0426002e7be7fcd6ee0abf3755e2babfe5636ca0b37", + "1d29b6d8eca793bb801becf90b7d7de215b17618ec32340da4bac707cdbb58b951d5036ec02e105d83b5960e2a72002d19b7fa8e1128cc7c5049ed1f76b82a59eac6ed09e56eb73d9ade38a6739f0e07155afa6ec0d9f5cf13c4b30f5f9a465b162a9c3ba04b5a0b3363c2a63f13f2a3b57c590ec6aa7f64f4dcf7f1582d0ca157eb3b3e53b20e306b1f24e9bda87397d413f01b453ceffeca1fb1e7", + "6a2860c110cd0fc5a19bcaafcd30762ee10242d34739638e716bd89fd537ea4dc630e6f85d1bd88a25ad3892ca554c232c9830bd56980c9f08d378d28f7fa6fa7df4fcbf6ad98b1adfff3ec1f63310e50f920c99a5200b8e64c2c2ca249399a149942261f737d5d72da949e914c024d57c4b639cb89990fed2b38a37e5bcd24d17ca12dfcd36ce04691fd03c32f6ed5de2a2191ed7c826375ba81f78d0", + "7132aa291ddc9210c60dbe7eb3c19f9053f2dd74742cf57fdc5df98312adbf4710a73245de4a0c3b24e21ab8b466a77ae29d15500d5142555ef3088cbccbe685ed9119a10755148f0b9f0dbcf02b2b9bcadc8517c88346ea4e78285e9cbab122f824cc18faf53b742a87c008bb6aa47eed8e1c8709b8c2b9adb4cc4f07fb423e5830a8e503ab4f7945a2a02ab0a019b65d4fd71dc364d07bdc6e637990e3", + "3e664da330f2c6007bff0d5101d88288aaacd3c07913c09e871cce16e55a39fde1ce4db6b8379977c46cce08983ca686778afe0a77a41baf447854b9aa286c398c2b83c95a127b053101b6799c1638e5efd67273b2618df6ec0b96d8d040e8c1ee01a99b9b5c8fe63fea2f749e6c90d31f6fae4e1469ac09884c4fe1a8539acb313f42c941224a0e79c059e18affc2bcb6724975c436f7bf949ebdd8aef51c", + "7a6ea63a271eb49470f5ce77519ed61ae9b2f1be07a96855726bc3df1d0723af3a703fdfc2e739c9d31d25814daf661a23558b50982e66ee37ad880f5c8f11c8130fac8a5d0250583700d5a324894fae6d61993f6bf9327214f8674649f355b23fd634940b2c467973a839e659169c773119919f5b81ee171edb2e5f6940d7551f9e5a70625d9ea88711ad0ed8ab2da720ad358bef954456cb2d5636425717c2", + "c5106bbda114168c449172e49590c7eeb827fa4e1a2a7a87a3c1f721a9047d0c0a50fbf244731be1b7eb1a2ef30f5ae846a9f38f0df44f32af61b68dbdcd0226e741dfb6ef81a2503691af5e4b3171f48c59ba4ef91eba344b5b697f261df7bbbb734ca6e6daebaa4a179feb17002823281b8534d55a6531c59305f6e3fd3fa63b747bcf0deb654c392a02fe687a269effb1238f38bcaea6b208b221c45fe7fbe7", + "597716a5ebeebc4bf524c15518816f0b5dcda39cc833c3d66b6368ce39f3fd02ceba8d12072bfe6137c68d3acd50c849873150928b320b4fbc31c1456679ea1d0acaeeabf666d1f1bad3e6b9312c5cbdecf9b799d3e30b0316bed5f41245107b693366accc8b2bcef2a6be54209ffabc0bb6f93377abdcd57d1b25a89e046f16d8fd00f99d1c0cd247aafa72234386ae484510c084ee609f08aad32a005a0a5710cb", + "0771ffe789f4135704b6970b617bae41666bc9a6939d47bd04282e140d5a861c44cf05e0aa57190f5b02e298f1431265a365d29e3127d6fccd86ec0df600e26bcdda2d8f487d2e4b38fbb20f1667591f9b5730930788f2691b9ee1564829d1ada15fffc53e785e0c5e5dd11705a5a71e390ca66f4a592785be188fefe89b4bd085b2024b22a210cb7f4a71c2ad215f082ec63746c7367c22aedb5601f513d9f1ffc1f3", + "be6556c94313739c115895a7bad2b620c0708e24f0390daa55521c31d2c6782acf41156271238885c367a57c72b4fe999c160e804ad58d8e565edbce14a2dd90e443eb80626b3eab9d7ab75d6f8a062d7ca89b7af8eb292c98eaf87ad1dfd0db103d1bb6188bd7e7a63502153cf3ce23d43b60c5782602bac8ad92fb2324f5a79453898c5de18415639ecc5c7974d3077f76fc1df5b956723bb19a624d7ea3ec13ba3d86", + "4bc33729f14cd2f1dc2ff459abee8f6860dda1062845e4adab78b53c835d106bdfa35dd9e77219eaef403d4e80488ca6bd1c93dd76ef9d543fbb7c8904dccc5f71509a6214f73d0f4e467c3e038ea639b29e7fc442ee29f57117740576188ada15a739827c647a46b0271817ab235c023c30c90f2115e5c90cd8501e7b286962fc66ffc3fe7e8978746168314908a41998bd83a1eeffda9d714b864f4d490fdeb9c7a6edfa", + "ab12faea205b3d3a803cf6cb32b9698c32301a1e7f7c6c23a20174c95e98b7c3cfe93fffb3c970face8f5751312a261741141b948d777b8a2ea286fe69fc8ac84d34116a4674bb09a1a0b6af90a748e511749de4697908f4acb22be08e96ebc58ab1690acf73914286c198a2b57f1dd70ea8a52325d3045b8bdfe9a09792521526b7564a2a5fcd01e291f1f8894017ce7d3e8a5dba15332fb410fcfc8d62195a48a9e7c86fc4", + "7d421e59a567af70594757a49809a9c22e07fe14061090b9a041875bb77933deae36c823a9b47044fa0599187c75426b6b5ed94982ab1af7882d9e952eca399ee80a8903c4bc8ebe7a0fb035b6b26a2a013536e57fa9c94b16f8c2753c9dd79fb568f638966b06da81ce87cd77ac0793b7a36c45b8687c995bf4414d28289dbee977e77bf05d931b4feaa359a397ca41be529910077c8d498e0e8fb06e8e660cc6ebf07b77a02f", + "0c18ab727725d62fd3a2714b7185c09faca130438eff1675b38beca7f93a6962d7b98cb300ea33067a2035cdd694348784aa2eda2f16c731eca119a050d3b3ce7d5c0fd6c234354a1da98c0642451922f670984d035f8c6f35031d6188bbeb31a95e99e21b26f6eb5e2af3c7f8eea426357b3b5f83e0029f4c4732bca366c9aa625748297f039327c276cd8d9c9bf692a47af098aa50ca97b99961bef8bc2a7a802e0b8cfdb84319", + "92d5909d18a8b2b9971cd1627b461e98a74ba377186a6a9df5bd133635250b300abccb2254cacb775df6d99f7c7d0952653c28e6909b9f9a45adce691f7adc1afffcd9b06e49f775364cc2c62825b9c1a86089080e26b57e732aac98d80d009bfe50df01b95205aa07ed8ec5c873da3b92d00d53af825aa64b3c634c5ece40bff152c331222d3453fd92e0ca17cef19ecb96a6eed4961b627aca48b12fecd091754f770d52ba861546", + "802f22e4a388e874927fef24c797408254e03910bab5bf372320207f8067f2b1ea543917d4a27df89f5bf936ba12e04302bde23119533d0976beca9e20cc16b4dbf17a2ddc44b66aba76c61ad59d5e90de02a88327ead0a8b75463a1a68e307a6e2e53ecc1986274b9ee80bc9f3140671d5285bc5fb57b281042a8978a1175900c6073fd7bd740122956602c1aa773dd2896674d0a6beab24454b107f7c847acb31a0d332b4dfc5e3f2f", + "3844fe65db11c92fb90bf15e2e0cd216b5b5be91604baf3b84a0ca480e41ecfaca3709b32f8c6e8761406a635b88eec91e075c48799a16ca08f295d9766d74475c47f3f2a274eae8a6ee1d191a7f37ee413a4bf42cad52acd5564a651715ae42ac2cddd52f819c692ecdef52ecb763270322cdca7bd5aef71428fa73e844568b96b43c89bf1ed42a0abf209ffad0eeec286c6f141e8af073ba4adfbbdeda253752ae36c9957dfc905b4c49", + "329377f7bf3c8d74991a7d61b0cf39baff5d485d79751b0d5ad017d23bec570fb19810105bab79ab5acb102ab972165224d4ec888ec7de5148077fa9c1bb6820e0d91ae4e2591a21fec2f820606ce4bafc1e377f8dc3a5bd1a9e2772a57abccd0b757164d768872c91d02789545ab5b203f688d71dd08522a3fd2f5bcd7df507aebf1ca27ddff0a82afb7aa9c180008f49d1325adf97d047e77238fc75f56356de4e87d8c961575c9f6362c9", + "f7f269929b0d71ea8eef7120e55ccba691c582dd534692abef35c0fe9dec7dae973cd9702e5ad420d278fe0e653fdcb22fdcb63148109ec7e94f2d0750b28157dd1764376ae10fdb0a4aef3b304bd82793e0595f941226a2d72abbc929f53134dc495b0d65ced409914f94c2523f3dfbbdeeac84ae247ab5d1b9ea33dce1a808885a55be1f3683b46f4be73d9b62eec2585f690056858dfc427aabf591cd276724885bcd4c00b93bb51fb7484d", + "ac022309aa2c4d7fb628255b8b7fb4c3e3ae64b1cb65e0de711a6def1653d95d8088871cb8905fe8ae76423604988a8f77589f3f776dc1e4b30dbe9dd262b2187db02518a132d219bd1a06ebac13132b5164b6c420b37dd2ccee7d69b3b7fa12e54f0a53b853d490a68379ea1fa2d79762830ffb71bf86aab506b51f85c4b6a41b69325c7d0c7aa85b93b7144489d213e8f33dbb879fce22849865337b620b155cb2d2d36a68832889e30194d36d", + "d009c2b78a8f02e5e5dbb586ef71fc324b375092e15913ca1a5bfd22d516baadb96867bee3562e77c4a4852344a1a76c30728be5e22400b4cc41711f66754c246a520498d8c24f0205b9c873748dbeb67fe1ad099ad04cf89f4b517f0aa481136d9f6de2d727df01c6aa4099da59d4382b51e25fd47c33d9842c32b62331e50794bfe8b61b3ba9de1b8b704779c6d65edff3af00f121ab4a7ea384edabe47c6d0098a48991f387ca4444135ec59d46", + "c00bab36cce69899817d1425016d222d7303197ed3e3fdcac744705e7f178a1ac745968900f69299163e19b3161f3e0a4cc55aa2e4e71e0ee6ac427d1f4d14e063f68d303ddfbb18118335cfa7a6a90d99c38319ee76f7a884846a9e0b68030bf28e78bfbd56359b9368842814da42b04cb0e307d5d846dc22f049147bae31b9a956d17676a8cc348dafa3cabc2007a30e730e3894dddf9999fb8819086311f0703e141613ed6dcd7af8510e2dc435b0", + "c9789152a9fc29698d49ed95f09bd11b75f18a8c5615a73dbe54ae5e550027fd0ae6a8b60667040c1b12de3d1ee3f6bf061c78c951a3210effc912e19f482dd4de152063c588c44903bc11761706fd935afa040df085b08144d83d0dde32b46ab52f4fae98ac116c7ff11d7f553450c2e37b9c5f0b1dd9e0b8640a24cba6f2a5246c41f197f46e3dc8a29131c79bef3351c6e277a0a34442274d546ccd058891277473d668420f121750d19cd684267405", + "06a15a0731ce52557e368bcbaa11ef3399299e36fb9f2eda6e5726907c1d29c5c6fc581405ba48c7e2e522206a8f128d7c1c939d1132a00bd7d6366aa82724e968964eb2e373563f607dfa649590dcf5589114df69da5547fef8d1604cc4c6de1ed5783c8746918a4dd31168d6bc8784cd0c769206bd803d6ca8557b66748770402b075ef44b38157d4c0da7c6281725a2065d087b1f7b23455fa673bdeeba45b983311c44eabe9ef4b7bde3420ae9881863", + "d08aacef2d7a41aec09473bd8a44f628e15addb7b9e5b77a1e09c8ab4942f379a0bfcb324d580b774666f18ae78dd36710824ff12393f059068fe4b559c53662c2b0e6c69e23785c8f32554e837ec1714bee902e60737b639dd933af4f68cb9d7de77e1f3b28e5b122891afce62b79acd5b1ab4ba411662cc77d806449e69c5a45a143b742d98ac84a0826d68433b9b700ace6cd472ba2d58a90847f42ce9c43f38ffc017db4bf40450b2eee1f4594dc740c0f", + "6a6058b0a498b7ea76a93c646eb9b8629f0cba4a0c726420c5f67ba9b0412cade356abdf0a4fb94384bad32ce0d5dd9e23dcaae1d6f28ff8683616b30f1392890c67b3a2c04b360893b801f127e527e4da82e239f4c878da13f4a4f1c76db07190e77ec123995168102fb274434a2d1e12913b9b5cbab4aacaad2bd89d88b3ca2b8e60dacf7c22c9379097ff60880f552e320ca3b571994f52534470feee2b39e0dadb5cd88257a3e459a4cc6f12f17b8d54e1bb", + "adeced01fc5671531cbb45679f5ddd42b3a95151677b6125aaf6f5e8f82fbabaa5ecf7c3552c2458587224f0042870f178f5fca5465250e75d71352e652eeed23cdb7f915f5ebb44099b6db116ca1be45530ac8ed32b7f161d60ed4397ad3d7d649ae6bf75ca5bec891d8e595605be9764f3a03965e1fe0eaffbf212e3df4f0fa35e08ff9d0091e6d4ac4748edfe43b611085a6ffec163014655fdd839fd9e81b63b1fa8cae4ec335ec343289758e389a79ceedfae", + "d014592f3a83ba40af366f137c674724916c3cdd3f6cf9d4c5c7c8d6d51ebf26e315e2c12b3546be56fb52382904046ecbd2f5b883aa4ff473de6f0c26ab862c3fa34bf3d880cc1911ce39a4088c6617c179dc5faf68a2c488bbde12d67b50f73abcfab0e3b062e68c95363e11f5f1de8ec36ed01ea21442518089045df67d346135283ad5b3fff80cf57f20876849f6db9fa139728358415a90610f69ec720fc92d8234e3e122551e9df2c644c4a2c4e3734d07de8e", + "c0d0c37838873ba8757d6e41b409605043bc1635edcd731219587676d94217e9f0ab44b71de25000661ce7303b7015f45e6eaa7b7ebef92b8f4a34c902c908d2172185505fa33aca5a41be83079316cdfdd430fc2c45f505f85d867e6d516f7e1bf19c001d9f43018968aab65ec031b3801399231c83ec9e622dab5629922a6b424cab938c135ff7310501c2c02971bfd2f577e25904d1a618baf0859f77f4e8b1d0cde9544e95ec52ff710c0672fdb3d891feeea2b017", + "7022e7f00902219ba97baa0e940e8ac7727f58955aa068c29680fac4a16bcd812c03eeb5adbcfe867a7f7c6b5d89f4641adb9173b76a1a8438866f9b4f640ce2aedf5f1080c890bcf515b4be4e3e512352f1e5323c62ec46cb73f3d71be8235fee55a154763f7c3f9aeb61ffd28f4cd93d3310f608e2133586bf1ab3f102de96f64c68a4668de8acb2a76a7ce0cddddc8fa3df5e9d230823da16ed9ebb402d36e38e6e018795e5a71517ecab5f9ca472b9ced8ff69d2d195", + "acaf4baf3681ab865ab9abfae41697141ead9d5e98523c2e0e1eeb6373dd15405242a3393611e19b693cabaa4e45ac866cc66663a6e898dc73095a4132d43fb78ff7166724f06562fc6c546c78f2d5087467fcfb780478ec871ac38d9516c2f62bdb66c00218747e959b24f1f1795fafe39ee4109a1f84e3f82e96436a3f8e2c74ef1a665b0daaa459c7a80757b52c905e2fb4e30c4a3f882e87bce35d70e2925a1671205c28c89886a49e045e31434abaab4a7aed077ff22c", + "84cb6ec8a2da4f6c3b15edf77f9af9e44e13d67acc17b24bd4c7a33980f37050c0301ba3aa15ad92efe842cd3ebd3636cf945bb1f199fe0682037b9dacf86f162dadabfa625239c37f8b8db9901df0e618ff56fa62a57499f7ba83baebc085eaf3dda850835520344a67e09419368d81012168e5de5ea45158397af9a5c6a1657b26f319b66f816cd2c28996547d697e8df2bb163ccb9dda4d6691dffd102a13667ab9cde60ffbfb872187d9c425a7f67c1d9fffff9276ed0aeb", + "6a52c9bbbba454c14540b2be58230d78ecbeb391646a0c6fcce2f789086a78364b81ae85d5396d7cfa8b46bda41e3083ec5cf7b4c47dc601c8a697df52f557defca248506dbebab25657f5a561d09625b7f4b2f0119a12beeac087efc9d350a735c35d2431c1da7dda99befb17f41a3dc4da0f00bb95366be128538ce27763d81f832fe3c1d4efc07b5b08ad8dc9e65fb5e48546664e18cb2d3bb3fe1f56fa7aae718c5e3bbdeaf70e15023f6a25b72a2d177fcfd04211d40664fe", + "c3c4d3b31f1f5f9538923df3478c84fffaef411520a542da9a220ee4132eabb9d718b5076fb2f985485e8ba058330aed27ddfd3afa3db34aa60301088caec3d0053828c0c2bc87e2e61db5ea5a29f62fdad9c8b5fc5063ec4ee865e5b2e35fac0c7a835d5f57a1b1079833c25fc38fcb14311c54f8a3bd251bca19342d69e5785f9c2e43cf189d421c76c8e8db925d70fa0fae5ee3a28c4047c23a2b8a167ce53f35ced33bec822b88b06f41558c47d4fed1bfa3e21eb060df4d8ba1", + "8d55e92136992ba23856c1aea109766fc44772477efc932b3194af2265e433ed77d63b44d2a1cff2e8680eff120a430fe012f0f09c6201d546e13ad46fc4ce910eab27bb1569879abed2d9c37fae9f1267c2216ec5debcb20d4de58461a621e6ce8946899de81c0add44d35e27b7982a97f2a5e6314901caebe41dbba35f48bc9244ca6dca2bdde7306435892f287036df088633a070c2e385815ab3e2bfc1a47c05a5b9fe0e80dd6e38e4713a70c8f82bd32475eea8400c7bc67f59cf", + "5016284e20362610fa05ca9d789cad25f6d43263787e7e085476764ce4a8908ce99b262b375e9d106170b1bec1f473d5e777e0c1896533040e39c8c1465e07907ef5860e14e4d8310013e35f12090e0bfc687474b1f15f3dd2033a0edac5246102da4deec7e188c3517d84d9c2a0a4497a4c5f82a30f1ba009e45ee6eb3ab4368c720ea6feee428ffd2c4cc52debb8d634a64176572c72368f94a66689f23f8a01218f532117af5a8060d140e7ca435a92882fcb5630ebe14a4805f1dc83", + "05456ec59b8d41bbd736727976b96b38c43827f9e16169be673ff37870c2ecd5f0d1ea1a136be4cc7b047a02a4421d484fd2a12ece418e42ee391a13a0b1df5a0162b29ab70d3fe3e04ba6ab26b37d62b7cf05a5e2f033611bf970b8e1f30e198e483e740fa9618c1e8677e07b61296b94a9787a68fba622d7653b5568f4a8628025939b0f74389ea8fced6098c065bf2a869fd8e07d705eadb53006be2abb716a3114ceb0236d7e916f037cb954cf977720855d12be76d900ca124a2a66bb", + "eb6f60b83fcee77060ff346aaf6ec34d82a8af469947d3b5074cde8eb26566eb1fa039bcc707738df1e95869bd827c246e88436f0614d9834ead5392ef376105c4a9f370071cdeaaff6ca0f18b74c3a48d19a717253c49bd9009ccbfdd5728a08b7d112a2ed8dbafbbb46d7a75dc9a05e09bfde1a0a92d74a51887f9d123d7896e9f9d0057b660ed7d55454c069d3c5260411db4cdc67e7b74f680d7ac4b9dcc2f8baf72e15e6b3cafebcdf449a6436ed2c398b675f79c644747c57553bf7ea2", + "187a88e88514f6c4157c1ba40b442baae1ae563a6c989277443b12a219aa484cb9fa8adbb9a29d429f50155321b15664926317477079c7060dfdaa84c1d74bba78892c34e6f21ad35208d2ae622012401696bff5cd57b6485944b3db7b9071fa5f57fbfb1085d91bb9cff5808d662cdc6c8157249478262c44b7fbc397ed42a4977b202e817717bfccc9f0467294062313f7705251ed09573f16d23429361fada259dfb300369c4198f07341b38e84d02cdb74af5de6aab1fc2026208ea7c418c0", + "be31bc96606d0fab007e5caeded2f1c9f747c759777e9b6eef962bed49e45a1d4fc993e279d024915e600865ecb087b960584be18c41114d3c43f92169b9e0e1f85a0ebcd4e196376ccdc920e66103cd3b1c58407d0aafd0e003c4e341a1daddb9f4faba974362a32f35db83384b05ae8e3322d728893861afd8b1c940de5a17f691e763ce4969b6d94f67fb4a0235d100225bd8602f291388f0ca4a568748ad0d6040f1262eac2aede6cd27419bb78a394c1ffad72c262be8c3f9d9619d633e51d0", + "4d83d85ca838b4518588f2a90228a4dd18f14dd5b4c012d26298a97d848abbd825d221d02cceb6e8c701b4ad00e1dee4889b5c533e4bb60f1f41a4a61ee5478be2c1b1016c30345afd7a5253668260515e70751f22c8b4022d7fe4877d7bbce90b46531507dd3e89549e7fd58ea28f4cb23d33662bd003c1345ba94cc4b06867f778957901a8c441bee0f3b12e16463a51f7e50690356971dd73a686a49fda1eae46c9d54fba262811d698025d0ee053f1c58591c3bb3cbde69de0b31549ef5b69cf10", + "cdeb07d36dc5f9a1cd717a9e9cca37a2ce93caa298eee63571f7d6c5fde2a11c666cf53cf2dcb41ca2ea2319e7230ca68e38c647905928713a13982bf47fe33d7095ebd50b2df976208920a43eb2e29b942f32467403c45cea18bf44e0f6aeb155b48a8e5c471fec972a9d62f7ae093d2758f0aaec7ca50cb4725bfa219f1a3a46ad6bde7361f445f86b94d66b8ece080e56c510250693a5d0ea0ae87b4421860b853bcf0381eae4f1bf7c5c0472a93ad18407bc88475ab8560d344a921d3e86a02da397", + "a598fad52852c5d51ae3b10528fc1f722e21d44fbd42ae5acdf20e85a28532e646a223d27fd907bfd38eb8bb75175636892f8242877aab89e8c0824d368f3339ce7a82aa4e5af6db1f3b588a4d667a00f67bee37cfd2724dde06d2909fb9e58d892f4cfd2c4ca85acdf8256f5458b030a6bda151154ff2e6d7a8da90b54a2884c8a99fab5a4ac211ff23dc0975f4f592fd1b6b9dc7783bdcd2d4ca4e68d2902f2013e122cb62e2bff6b0a98ec55ba25837e21f1cfe67739b568d43e6413dab2bd1dc471e5a", + "17b68c74c9fe4926e8102070916a4e381b9fe25f5973c9bd4b04ce25749fc18931f37a65a356d3f5e5a1ef125d546f4f0ea797c15fb2efea6fbfcc5739c564693d47adeb12dcb3d98a2830719b13247792cb2491dca159a28138c6cff925aca42f4fdb02e73fbd508ec49b25c60703a7595a3e8f44b155b371d525e48e7e5dc84ac7b17c52bf5e526a67e7187234a2f19f57c548c70fc0b27183df73ffa53fa58b658034c896fa791ae9a7fd2620f5e46ce84c842a6e60e9324ae4db224ffc87d9617cb85ca2", + "b9e4267ea39e1de1fed0579f93bb351007c9f8fcdd811053fae33f09e2753d7428f04e1a9efcd45ea701a5d87a35b3afb2e6b65365dee6ead0bbb611b7797b212ac688653f542e604a39df277f12514ddfee3b4e27b98395c2cd97a203f1f1153c50327965770802ec2c9783edc428271762b275471e7ac65ac36523df28b0d7e6e6ccc7674268a132a63411fc82c0738dbb68af003b769a0bf9e6587b36476cb465350fee13f88ea355d47ffac7b0f964f4139db11b7642cb8d75fe1bc74d859b6d9e884f75ac", + "8ca704fe7208fe5f9c23110c0b3b4eee0ef632cae82bda68d8db2436ad409aa05cf159223586e1e6d8bdae9f316ea786809fbe7fe81ec61c61552d3a83cd6beaf652d1263862664df6aae321d0323440430f400f291c3efbe5d5c690b0cc6b0bf871b3933befb40bc870e2ee1ebb68025a2dcc11b68daadef6be29b5f21e440374301bde1e80dcfade4c9d681480e65ec494a6af48df232c3d51447b9d06be714949249c44c43cf73ed13ef0d533e770284e51369d94ae241a5fb2f163893071b2b4c118aeaf9eae", + "4fd8dd01012bb4df82bf42e0683f998e6f52dd9c5617bae33f867d6c0b69798cead8179346d70acc941abbbdd26e3229d5651361d2252c72ff22db2938d06ff6fc29a42fdf800ae967d06479bc7bbb8e71f40b1190a4b7189ffc9a7096cdb76d40aec424e1388e1eb7ef4ac3b34f3f089da8fda7d1927f5d775c0b2801d22dd1265c973158f640cec93edfed06dc80b20ef8c496b98289d54d46ccd205951cbb0f4e7daeb866b60bacb483411e4382b6f04d472843186bd0e31fbaa93e5c901ec028efafeb45fc551a", + "e9ee1b22b04b321a5fdd8301627011f583887d77560fb0f35552e207561f81e38ac58a0d0aeaf832d1ee72d913720d01f75574e9a321864fe95f4d0d8f0b8db97649a53e71e940aede5c40b4b9105daa42a6fb2811b61209247534cbaf830b07abe338d75d2f5f4eb1c3cf151e9edabe2c8d5f6fff08fac1495ef48160b100d30dcb0676700bcceb28723a29980ab0766a93abb8cb3d1963007db8458ed99b689d2a7c28c788743c80e8c1239b20982c81dadd0eed6740c65fbc4ef15c7b5569cb9fc997c6550a34b3b2", + "ec01e3a60964360f7f23ab0b22e021815765ad706f242265ebc19a2bb9e4eac94393952dcf61aae47682671a10f9165f0b20adf83a6706bfbdcf04c6faba6114653a35584267267873291c6fe7ff5f7695243143421509502c8875aafa9e9afe5be5ef2c851c7f35d69be5d3896000ccdbbfab5c238bb34d607cfe2d55d748880545b4aa7ca61137992925189025c62654b1f20d49c3ccd75aa73ce99cd7258dabedd6480a9f5185531fc0118beb68cc0a9cd182f6973287cf9252e12be5b619f15c25b65c71b7a316ebfd", + "db51a2f84704b78414093aa93708ec5e78573595c6e3a16c9e15744fa0f98ec78a1b3ed1e16f9717c01f6cab1bff0d56367ffc516c2e33261074935e0735ccf0d018744b4d28450f9a4db0dcf7ff504d3183aa967f76a507357948da9018fc38f150db53e2df6cea14466f03792f8bc11bdb5266dd6d508cde9e12ff04305c0295de29de19d491ad86e766774bb517e7e65befb1c5e2c267f013e235d8483e177214f89978b4cdc81aa7eff8b39f2825ad3a1b6ac1424e30edd49b067d770f16e74dd7a9c3af2ad74289a676", + "00e40f30ae3746edad0f5dd03d0e640933cf3d1694804c1e1ed6399ac36611d405196ee48f129344a8512feda16a354517871322bd5d9c6a1b592933eab531923efb393ffb23d9109cbe1075cebfa5fb917b40df028a621460ff6783c798792cb1d9635b5a6f84ec13918fa302924649b5c7fcb1f7007f0d2f06e9cfd7c27491e565a96c68a0c3644f92cd8f38857258c33801c5d537a83dfe583cba59d7eec7e394199c0a2660a62fabe3ed2099d57f315a6cd8de1a4ade29d977f15d65759cff433e5ac0c182aef3761163e1", + "3c5ea24d0d9b618294a263f062b2414a722be4eb10dfc346a6ec3b821d7396eba61cd6ef33618b04cd087a811f299d4606820227f16000d7c839062b96d3e3f59cd1a082448d13fc8f56b3fa7fb5f66d0350aa3b72dd7c165d590282f7da2e12cfe9e60e1796122bb8c2d40fdc2997af634b9c6b127a893dfb3467909378300db3da911be1d7b616bb8e0572433e65527e15d936500a2c60e9f9909dcf22ab5e4b6700f0238c205b4a813626fac3d945bab2637fb08203044a73d20c9a3fcf7c3fc4eb7807c3276dd5f73ce89597", + "9271aeeebfac46f4de85df78f1bfd36136aa8905e15835c9e1941176f71e3aa5b1b131843d40479735e23e182a2bd71f66f6149dccb7ed8c16469079dc8590bbf165374951785f4531f7e7361de62f936cfb23a2b5bdf186632e7042a0dd451fdc9b7208f923f3a5f250ae590ec348c63a16c3aacaf7379f53b5dd4152dcd40d23e683e2156e64c592ffc07e2cd6bbeebef4dd590b2f6b2bcbf08fcd111c079f5c4033adb6c17574f8756ecd87be27eff1d7c8e8d0324438d59ae171d5a17128fbcb5533d921bd044a2038a5046b33", + "4e3e533d5bcb15793d1b9d0468aaee801f32fdb486b11027183553a09ddbee8213924296f2815dc61577297459e834bf1c7a53f87d43782209e589b8295219ba7073a8fff18ad647fdb474fa39e1faa69911bf83438d5f64fe52f38ce6a991f25812c8f548de7bf2fdea7e9b4782beb4011d3567184c817521a2ba0ebad75b892f7f8e35d68b099827a1b08a84ec5e8125651d6f260295684d0ab1011a9209d2bdeb75128bf5364774d7df91e0746b7b08bda9185035f4f226e7d0a1946fcaa9c607a66b185d8546aac2800e85b74e67", + "b5d89fa2d94531093365d1259cc6fe8827fea48e6374c8b9a8c4d2209c280fa5c44958a1847222a692a59e6aa2696e6cdc8a543dd89b0ce03bc293b4e78d6ef48e1839694ccd5c65661143095c705b07e3ced84a0f5959114dd89deb956ab3fac8130eb4a878278205b801ae41a29e34146192308c4e759b374757b0c3b00319bce92a1b95a4d2ee179fd6714ff96155d26f693a5bc973f84ac8b3b91e3926276297532d98b46992a3f104c08100bf1671c43134bac280c617da711e90a0100137525375ebb12802a428885ae7fce6514a", + "40e3d8048fc10650cb8a7fc2e7113e26dec34f9ca2d5129cd10a8e8e44d113d61ee48c7d003e19fd307fc6debd70feb30243f298c510ccc4418355ce143066f067ad7c6de7288c3080e7ad46a23c8d34deb55a43e652fe90444ad3c57d3ec1e1c489d63ef915a24bc74a7925a0a7b1e1523f21ca8fee78df24e3d0a68d0013423db97c280799a0618229c0f2c167289a891e5c8d6661ab21285951c31710e3b5fe55f6347fe16d9b40507948a59252efeb616df83e5c098b07d0a7247cd371daff0e50491c582503fd89f79ba94d6af9ed76", + "1fa444de01dd3901e2b4684e3d7a799ffa02d85afd35fb30fe4c9d672837bee6dd8a3b8608b4bb5e589220ad5a854f46b46e41c6d57ad124a46beab4169ff69fee7e3838a6165e19dad8eb5d7bf53d4edd3cd2769daf219510a02fdd2afe0c0e1da3cd30fcd1aa88b68965586f07a25a1720fbd90a096ea30fc8e945e3637d7857c8a9c0ab4154ffb2000e57b5f9adfa4e4eaf8065bc3c2b2e75f495963325588785a6ce417dcddffd299873b15dcccca128d63cd4eeeadb64cda28099a9ad7c80d34844901f26b88b00b9aafeb2f90286d29d", + "fde0a0d9d813983bd1f55cf778a003a2023b34a555322ab280584537bc6bdd844d22a7d6066c18da83ec09f3d8d5a1aab4be0d5ce19b436052f6e259a4b49017a1f47f1fe2bf115d5bc8599fb216351c60dd6b1bedb2e6f4dcadf424b833501b6f099cbfad9e2290680fb69c25032b42a6274f7cb9b5c5950401354838a45f7cb77b95bf54718e2f3d3d9fb91eb2311903980277396398d9736d8e92fd838594ac8a537c6c529db5a8a4f89290e6ba6f20ac0e5ed6fef40901d0e0e8e3e502990811f9acaae555dd54eb1bcd96b513e2fe751bec", + "9f8e0caec87858599f5ab29bff86da78a841a918a023a111098687ecdf2747612d3f3809d9ca400b878bd4f92c43a1004f1c17c7f19a3cd1ce449bd2b23aff551623c37dd8c0be56bf3fd857b500c2b9f9ccea62481944090a3cf3b6ee81d9af8eeb60f65ef150f9fa4d3ed6ce4762d3d4f174ee8ccd460c25cafac0ea5ec8a6a4b2f9e8c0520cb7061155e532cb65f188b01e4b9086db951f504b060c296b326b3fc1c590498ecce594f828f4a10ea416675720ae505295d38a791bd0e93f428448a8f4c1fc0af53604a9e8255384d29ae5c334e2", + "33d1e683a4c97ee6bbaa5f9df1a88cb53b7f3c157b6045d70a56fda0ccbd3a1fa1f049cd564da072b53f415bf5fb843771c1d2551fd075d33377362b2f7c0645f9723123d11975991db8a2b518f02e2c7c30342a044754290bae2c77496d755e5981f12e6b0a0174280b958bf11ed628a9062775993ced04bf752ea8d165e3ac2177d7cd1b9371c44efa98f0b3e68602a839d384eec007979f46429dafb138cbc231ad928a9f65f7d66fac77416395e8f1debaaf76ec2e4e03e8674102cd26f614739f3ec9f949033df1fb97e87c2326d65aef94ed5f", + "180048f09d0b480887af7fd548a85abf605440c1ddde6afe4c30c30670233f7bf928f43b4681f59279ebbda5e8f8f2a1abefdee129e18ac60f9224e90b38b0aabd01308e0a27f41b6fb2ee07ee176ec9048c5fe33c3f7c791469c81f30e28170585b9f3e7e3c8c2e9d74370cb4518f13bf2dee048cbd98ffa32d85e43bcc64a626b40efb51ce712925fdd6fee006dc68b88004a81549d2121986dd1966084cd654a7c6686b3bae32afbd9625e09344e85cf9611ea08dfce835a2e5b3726e69ae8a76a97db60fcc539944ba4b1e8449e4d9802ae99fae86", + "13c0bc2f5eb887cd90eae426143764cf82b3545998c386007cca871890912217aa143ac4ed4ddb5a7495b704aa4de18419b8664b15bc26cfc6596a4d2ae408f98b47a566476d5802d594ba84c2f538def9d016661f6404bb2337a3932a24f6e30073a6c9c274b940c62c727242e24466084a3ea336365d71ea8fa6499c0ea8d59eea505f1126b99c795023c4963aa0d99323d0391e8701110edf551b2d3799e1063ca443f1add162156e445502ca1a052fe70c289838593b58839fc63de128a03e2bbf389e22ae0cf957fd03315ee407b096cc1cfd92dee6", + "6f1eb607d679efef065df08987a1174aab41bdac8aece7726dfa65805d6fff5b3d17a672d96b770dc32165f144f0f7324822a5c87563b7cd9e37a742ae83ef245d09006d91576f435a03476f509ea2936636232f66aa7f6cdf1ac187bbd1fcb8e20f8791866e60ed96c73374c12ac16795e999b891c64507d2dbd97e5fc29fac750ad27f2937cbcd29fdafccf27ab22453834d475f6186eaf975a36fad5c8bd61c21da554e1ded46c4c39765dcf5c8f5ccfb49b6a4dc562c919d0c7d8940ec536ab2448ec3c9a9c8b0e8fd4870cad9de2577c7b0c38563f355", + "dcdd993c94d3acbc555f464871a32c5da6f13b3d5bbc3e34429705e8ad2e76393fdd96a69a94acb652f5dc3c120d41187e9aa919669f727c4868013b0cb6acc165c1b7706c52248e15c3bf81eb6c147619467945c7c48fa14a73e7c3d5bec91706c567145342a026c9d97eff97ec672c5debb9df1a998083b0b0081d65c517b3e5634c95e347e781aa30ca1c8af815e2e494d844e847fdcb41622894a518dc36571123a40bfdbe8c4f4cff44d83c61dd9dcd24c464c53b395edb31efee9f3aa080e87cdc3d22d613ae84a53c9249c32c96f9a3bc4629bb126a70", + "49971f9823e63c3a72574d977953329e813b22a8387cd13f56d8ea77a5d1a8a20012632d1d8732bbcb9f756b9675aab5db927beacab7ca263e5718b8dfa7b2eed9a91bf5ed163b16139d45f7b8cc7e3f7bdda6202106f67dfb23b7c315ee3e17a09d466b1e6b13e7c7428184a979f5358667b4fa8bd40bcc8ea46058db44587a85377ac46bf155136c09ac58cb6c27f28e17028c91e7e8f74d5b500e56293b316974f02b9d9ea205d9b6ac4cfb74eb8eb0c944577fd2f41316368307beab3e327bf7dbaa0a4428836ec4e895dea635234abeaf113ceeadac33c7a3", + "c57a9cc958cee983599b04fe694f15fb470fcbc53e4bfcc00a27351b12d5d2434444253ad4184e87b81b738922ffd7ff1dc1e54f39c5518b49fb8fe50d63e3935f99e4bd125e8dc0ba8a17fd62de709339a43fabe15cf86d96a54010112170c340cfac4132182eed7301402bc7c8276089dec38488af145cb6222525894658f03501204b7a66aba0be1b557b28a2f652d66f7313ed825ecc4d8596c1be7420d4425b86a1a90a5b7f30d0f24e0d1aae0eb619ca457a71699e44be612a4011c597ee80b94d5507e429d7fc6af22579cd6ad642723b05ef169fade526fb", + "0568a672cd1ecbaa947045b712e2ac27995392fbef8f9488f79803cbee561c212287f080eca95adb5ba42739d78e3ba667f06045d87850d3a0499358649caa257ad29f1a9c511e7054db20554d15cbb55ff854afa45cae475c729cea72ede953522031865bc02b95589ed4d9841c552a8cc94904a93ed09ed77222f6c178195056be59bc4e96a815adf534e6b466fb47e262ff79c803c157a21b6e2269c2e0abeb494113cd868d8466e82d4b2f6a28b73645853d96bc9242515d803e33294848d3fe42fdff68da53c03491636beede47ff1399dd3d54a5e914d55d7adf", + "3f19f61a4cd085796731ac9f85a75a8bce77031932c31762d87d8b8d07b8bd19ff78d6b7d1bd1e87f3a4f41aad03b6c4d17a6cbc86be55f7c8b88ada047bb04f8d49f1c34bcf81cc0f3389ad01a758fc7eeb0072aa9ad1481992bfdde82e438e75590a4423832dfbe3756e2229ea873bc3606e6d72174cb2163bf40b5d49c81009dab85ecc03e311351bbf96e32c030a2b276a7698cb25bc2c967acb3213161a1fdde7d912cd6a804490f8056c47da1333f6e35c41e749c2c23919cb9af5eec5652e6e072b034fb1682e9aaa194a9c0bd456ea0b008d14dbce37967a7a8e", + "705f98f632d99d3651793825c38dc4deda56c59eac539da6a0159c83131cf8ab6f2ee0c3b74111fde351f7aa1a8c500a0cecab17c212d2c58ca09eae608c8eefc922b9902ef8d6832f799ba48c3c28aa702b3242107edeba01daafe424406a3822965056cfe8783455a671e93b1e2eae2321364f1871471c82124df33bc09e1b52882bd7e1c4c7d0b2f3dd4a28c2a002a43246768af0700f9659de99d62167be93177aabf19d678e79e9c726ac510d94e74873eda99620a3961930cd91937c88a06d8153d64fd60da7ca38cf26d1d4f04a0df273f52127c53fdc593f0f8df9", + "ea6f8e977c954657b45f25480ff42c36c7a10c77caa26eb1c907062e24fbca5aebc65cacca0de10abea8c78322f08672e13d8ac16996eca1aa17402eaea4c1cc6c800b22dc18cb8d620192d74bac02c07b5cfa61e513c7f28b7e29b9700e0e442720bf4c669d4995da19d19f841d9eb68cc74153592591e3bf059ef616b95305aa453b32fe99a91afb35bd482cf2b7aa42702837a53be3c38883d2963020e347556f841254ec6b85854485fe8c520b05f2ea67a9bf3981555c20991e2bacd4db5b418228b6002d8d41c025cb472bf5443aaa885974a408ea7f2e3f932c600deb", + "408190134ed06556811b1af808ab2d986aff152a28de2c41a2207c0ccc18125ac20f48384de89ea7c80cda1da14e60cc1599943646b4c0082bbcda2d9fa55a13e9df2934edf15eb4fd41f25fa3dd706ab6de522ed351b106321e494e7a27d5f7caf44ec6fadf1122d227eefc0f57aefc140d2c63d07dcbfd65790b1099745ed042cfd1548242076b98e616b76ff0d53db5179df8dd62c06a36a8b9e95a671e2a9b9dd3fb187a31ae5828d218ec5851913e0b52e2532bd4bf9e7b349f32de2b6d5d3cdf9f372d49617b6220c93c05962327e99a0480488443349f0fd54c1860f7c8", + "5f9e5c6f38573a85010a9d84d33f29c057003b2645e3ea6f72cbc7af95d197ce6a06b13fea81722853e6991791b8b15091cd066f5ed913592ed3d3af5370d39ba22beeb2a582a414b16824b77e194a094c2afdcc09aa73ce36f4943cca5ae32c5017dc398801dd92a47382d9327c9f6cffd38ca4167cd836f7855fc5ff048d8efba378cdde224905a0425e6b1de061fc951c5e624a5153b008ad41160a710b3ff2081748d5e02deb9f841f4fc6cf4a15153dd4fe874fd447482696283e79ee0e6bc8c1c0409baa5ab02c5209c319e3169b2476149c0c6e541c6197ca46e004eef533", + "218c6b3508aec69574f2b5039b30b942b72a8349d05f48ff945bbbe5c8957d5a6199492a6bf54bab821c9377e2edfa4c908384664d2c80112d5e805d66e0a551b941021be17dd20bd825bea9a3b6afb1b8c605805b3bda58750f03ea5c953a698494b425d8980c69f34d1c3f6b5866e8717031152a127215c256e08873c21b0f5cc85875d0f7c94601659150c04cd5fe5d381ba29983a2d94fcd3a65a94c53c7279cd000dddd4253d8cff8d7f6ace10247fe3bc30d63ba4bb54f557b3d22a3924369430d71ab37b701e9500bda70b5a643704858beed4726a889b6c9c91584194c68f1", + "dac26aa7273fc25d6e044c79fc2bfa46e59892a42bbca59a86826c91e76ab03e4bd9f7c0b5f08d1931d88b36ea77d94f7ba67cd4f1d3086e529427201119096ae066ae6f170940830ed7900de7bb9d66e09788287403a4ecc93c6da975d2fb08e918840a236c15f5d3a8f7375c2eeebbf6f01a6e7f29ca2b8d42df158414c320777433663c59fdcd1f39ca68e3473db721be7ce8c6dba5fddc024f94fedb286b0477581d451313ca8c737484daf60d67f9b2d56d4bcc271f7e9ae958c7f258efbc74d25753e0516f28282461941bf2dcc7dd8c7df6173b89760cefcac07190243ff863fb", + "c46e6512e6797cc7a54254a1b26b2de29aa83d6c4b1ea5a2786fbcec388270625b12635eae39e1fba013f8a65219421bca8b52a8ddfd431cda60299bdf160734d5a7450ec79620058522702174ae451b9bfa7c4a455fbbee3e1d048c7d4bac5131018228f137c8e130440c7059b4f15eaa34ce872a851a16ce86f982df78a00be4d564da2003a450ddee9ab43ea876b8b4b65c84f0b39265fd5456417afb5bc54997c986e66fc222f2123ba5e719c4d6b9a177b188277df384f1125821cf19d5248cef0be183ccdc84ac194506f740ed2188b2689ea4c9236a9e9e3a2fff85b6af4e9b49a3", + "1ccd4d278d67b65cf2564ecd4de1b55fe07adc80e1f735fe2f08ea53fd3977323689122c29c798957abaff6aba09bdcbf661d77f4dc8913ab1fe2bef38846166e3834785e7105d746484eff8c656af5d8c7854abc1c62b7fadb65521dc6f793d978bda9838eb3800417d32e8a24d8c8cb1d18a5de6ca79d9e1b0ff9aa25e6218fe944cf18666fecc1e31334b390260dbe0997539e1b02f6366b2aea4f4a21efe04f4b97568fcb39e59919d5ebac6543d5d0f48fc66b923c34aac377dc95c20329b837b6ed5e8d9a3d2089cd0d8f025658006ff41cbdaccca618822ca590ab155253f8bc1c7f5", + "9875209588395ee3c9fdd793fd48717cc84c8c3ea622b2ccc4a1be4448e6034b7810569855255031f10be5ffd714b05f9ce01972d712d40abf03d4d0ce175813a7a668f761324996093fc2aa5912f7fc2abdadd8775d2b4d9ad492216293381460ed8f6db3d641d1525f4242c348bbfe504c704f215dc461de51b5c75c1aae967936963848f16c673eca5e78dfd47eb19001d52d1bcf96c98956dad5ddf594a5da757e7ca35f2f69803b784e66ac5a58b75c228b8266ec592505e5d1ca87d81225738855f15bc0914677e81593fd409e77d159f8a908f67788de9eb06c5561547aada96c47c535", + "40c90e375e366f3756d89091eb3eed9fe0fbfc5638700af4617d358812bac53124a2205dd6756456787d49cd6a35e302479a0992288f47532e4ea7ab62fc5ad5adc690a5d9a446f7e035ad4641bd8dae83946aee3338ec984ccb5cc633e1409f2531eeffe05532a8b0062ba99454c9aeabf8ecb94db195af7032bfebc22912f49d39330add47ff8fa5720612d697f0b602738930e060a1bb214efc5e292224cf34e29deaea6b1b1ff847e94ecc997325ac38df61db45d82bf0e74a664d2fe085c20b04c39e90d6a170b68d2f1d373f00c731c524456ada73d659aaac9df3191a7a3865083343fc13", + "e8800d82e072210ca6d7fa2472028974780b76aad4bcb9ad362422dd05ae3232668251d164daa375a43b26a38cce28dbeb3dee1a4a579f70d0fe7febb29b5ece8aa836e050fb3d188c63aa9c3c0da6c717d86458a6096b5effceb964efdec7035960c09ccd10dea3c5f1c7f9f478d5887ebbe2e15c5ff85dbacbc444bb951c4eec7abecb89ed80187e409e2972ffe1a5f01562af109f2cf09471cf72cf83a3bb8f4e2ef38ed0e326b698296394e5b2718a5000c01425708e8ad0461e62462d8819c2377f13ab1be2c7c9f33dc06fe23cad27b87569f2ce2e56e4b2c60c7b1b3d370841d89ebdc1f192", + "796d6d1447d5b7e8c55cd8b2f8b7010db39f27565f907e3fc0e464ea2d4bb52b37f10e7c6dcfc59231b9cdee12c32aeb4adbc42b86e86eb6defb5b69e6ca75e1f4d0dae3e124e5a1b8b6697f7e10b0403f1f0a5ff848eef3752837a9ba17780f16a9a709188a8d5b89a2fa74adb2e651163b1c2b3d261e225c9158dcd9eb7ac3d6704cee290cdff6bcb3cb90cee030aa0d19d4693655c3c30ac6fc06d2ae37787c47126d57ed9a6bef5f8a6c56859aefc08755739a95aac57a4dd916a92ba9f3afbf969df8085949615033365c751a9a3e1a18cee98a69d22e64009bebf8307169b6c61de0617ecfafdf", + "4f9057183566153cf337b07c3f5556006de54c56b2a1e5326c07aaeabd1886ec6f1641358925db232b2f0dbf75229c796a7395b2f934c1f99090bec1123f3c841b1cb3c5b1ec42ed5408f2940f0c48a9470b852c46d6557853d459cecd2c32bbcd8ee21fa11e385eef0857cba4d8545a61b52a484cdd779db4739fbc7aa9860dcabe0488b98fa0b60c3f7d6153db279000a52ffb573dab37d2ab1896a90e5deb7ac6bbe56239085c325d83a917dc6e8a448425b718c2356b9f3066163555ec444f372e184e02c8c4c69b1c1c2ae2b51e45b98f73d933d18750968945ca85d6bbb22014b4c4015262e3c40d", + "79dcca7d8b81a61359e4aece21f3df7b99518ce70bd2f57a18bab5e7114af2add0a0cea7f319d69f231f060e0a539d9a23fb3e95451ce8c6340cfb09edf931df84203a39226dd9eb278f11b691ef612585b973daab373e65d11325898badf6732100371fd759960fa8fec373268421d28bffdb9b12a430b92fe4b07566ca0c89e616e49f8fc75ccd9cdc66db820d7c02e109aa5ed86b89770262918a518f90a2292f6b68d68ae03992e4259a17a23c84ec2a417f082b5abf3a26e44d2278ecb8ba9456965303a75f25394d1aaf5544590e74b14d8a4cc4050be2b0ebcfe4d2db6b12a02c68a3bcdda70301f3", + "848755dc31e25e9a42f9ec12d847d19f292c14c162c9aba49e972cb123b58b8e57bb263a923929833373858594ff52dbc298dbbc078599194e4c07b0e5fc1e10808bbacdb6e93c72b333685cf961f28eb0d5a395c63266b01f130d25db384b356e5da6d01042fc2359581b89c63b3bb2d1ce897fbc9e83fe85d9666cb60e6a8c657f70caad5387b8a045bf91095606802c8424ea8ac52ef29386dc46183378a5fcb2cb927428b8c070f1c42aafd3bc70ca25437807696a46873cfeb7b80ba2ebc3c4272443d445e46343a1465253a9eebd532a0d1d2c18264b91ff45159f245404ae9335f2af55c802772426b4", + "ecaa6e999ef355a0768730edb835db411829a3764f79d764bb5682af6d00f51b313e017b83fffe2e332cd4a3de0a81d6a52084d5748346a1f81eb9b183ff6d93d05edc00e938d001c90872dfe234e8dd085f639af168af4a07e18f1c56ca6c7c1addffc4a70eb4660666dda0321636c3f83479ad3b64e23d749620413a2ecdcc52ad4e6e63f2b817ce99c15b5d2da3792721d7158297cce65e0c04fe810d7e2434b969e4c7892b3840623e153576356e9a696fd9e7a801c25de621a7849da3f99158d3d09bf039f43c510c8ffb00fa3e9a3c12d2c8062dd25b8dabe53d8581e30427e81c3dfc2d455352487e1255", + "23a3fe80e3636313fdf922a1359514d9f31775e1adf24285e8001c04dbce866df055edf25b506e18953492a173ba5aa0c1ec758123406a97025ba9b6b7a97eb14734424d1a7841ec0eaeba0051d6e9734263bea1af9895a3b8c83d8c854da2ae7832bdd7c285b73f8113c3821cced38b3656b4e6369a9f8327cd368f04128f1d78b6b4260f55995277feffa15e34532cd0306c1f47354667c17018ee012a791af2dbbc7afc92c388008c601740cccbbe66f1eb06ea657e9d478066c2bd2093ab62cd94abadc002722f50968e8acf361658fc64f50685a5b1b004888b3b4f64a4ddb67bec7e4ac64c9ee8deeda896b9", + "758f3567cd992228386a1c01930f7c52a9dcce28fdc1aaa54b0fed97d9a54f1df805f31bac12d559e90a2063cd7df8311a148f6904f78c5440f75e49877c0c0855d59c7f7ee52837e6ef3e54a568a7b38a0d5b896e298c8e46a56d24d8cabda8aeff85a622a3e7c87483ba921f34156defd185f608e2241224286e38121a162c2ba7604f68484717196f6628861a948180e8f06c6cc1ec66d032cf8d16da039cd74277cde31e535bc1692a44046e16881c954af3cd91dc49b443a3680e4bc42a954a46ebd1368b1398edd7580f935514b15c7fbfa9b40048a35122283af731f5e460aa85b66e65f49a9d158699bd2870", + "fe511e86971cea2b6af91b2afa898d9b067fa71780790bb409189f5debe719f405e16acf7c4306a6e6ac5cd535290efe088943b9e6c5d25bfc508023c1b105d20d57252fee8cdbddb4d34a6ec2f72e8d55be55afcafd2e922ab8c31888bec4e816d04f0b2cd23df6e04720969c5152b3563c6da37e4608554cc7b8715bc10aba6a2e3b6fbcd35408df0dd73a9076bfad32b741fcdb0edfb563b3f753508b9b26f0a91673255f9bcda2b9a120f6bfa0632b6551ca517d846a747b66ebda1b2170891ece94c19ce8bf682cc94afdf0053fba4e4f0530935c07cdd6f879c999a8c4328ef6d3e0a37974a230ada83910604337", + "a6024f5b959698c0de45f4f29e1803f99dc8112989c536e5a1337e281bc856ff721e986de183d7b0ea9eb61166830ae5d6d6bc857dc833ff189b52889b8e2bd3f35b4937624d9b36dc5f19db44f0772508029784c7dac9568d28609058bc437e2f79f95b12307d8a8fb042d7fd6ee910a9e8df609ede3283f958ba918a9925a0b1d0f9f9f232062315f28a52cbd60e71c09d83e0f6600f508f0ae8ad7642c080ffc618fcd2314e26f67f1529342569f6df37017f7e3b2dac32ad88d56d175ab22205ee7e3ee94720d76933a21132e110fefbb0689a3adbaa4c685f43652136d09b3a359b5c671e38f11915cb5612db2ae294", + "af6de0e227bd78494acb559ddf34d8a7d55a03912384831be21c38376f39cda8a864aff7a48aed758f6bdf777779a669068a75ce82a06f6b3325c855ed83daf5513a078a61f7dc6c1622a633367e5f3a33e765c8ec5d8d54f48494006fdbf8922063e5340013e312871b7f8f8e5ea439c0d4cb78e2f19dd11f010729b692c65dd0d347f0ce53de9d849224666ea2f6487f1c6f953e8f9dbfd3d6de291c3e9d045e633cfd83c89d2f2327d0b2f31f72ac1604a3db1febc5f22cad08153278047210cc2894582c251a014c652e3951593e70e52a5d7451be8924b64f85c8247dab6268d24710b39fc1c07b4ac829fbda34ed79b5", + "d7314e8b1ff82100b8f5870da62b61c31ab37ace9e6a7b6f7d294571523783c1fdedcbc00dd487dd6f848c34aab493507d07071b5eb59d1a2346068c7f356755fbde3d2cab67514f8c3a12d6ff9f96a977a9ac9263491bd33122a904da5386b943d35a6ba383932df07f259b6b45f69e9b27b4ca124fb3ae143d709853eed86690bc2754d5f8865c355a44b5279d8eb31cdc00f7407fb5f5b34edc57fc7ace943565da2222dc80632ccf42f2f125ceb19714ea964c2e50603c9f8960c3f27c2ed0e18a559931c4352bd7422109a28c5e145003f55c9b7c664fdc985168868950396eaf6fefc7b73d815c1aca721d7c67da632925", + "2928b55c0e4d0f5cb4b60af59e9a702e3d616a8cf427c8bb03981fb8c29026d8f7d89161f36c11654f9a5e8ccb703595a58d671ecdc22c6a784abe363158682be4643002a7da5c9d268a30ea9a8d4cc24f562ab59f55c2b43af7dbcecc7e5ebe7494e82d74145a1e7d442125eb0431c5ea0939b27afa47f8ca97849f341f707660c7fbe49b7a0712fbcb6f7562ae2961425f27c7779c7534ecdeb8047ff3cb89a25159f3e1cefe42f9ef16426241f2c4d62c11d7ac43c4500dfcd184436bb4ef33260366f875230f26d81613c334dbda4736ba9d1d2966502914ec01bbe72d885606ec11da7a2cb01b29d35eebedbb0ecc73ed6c35", + "fd993f50e8a68c7b2c7f87511ce65b93c0aa94dcbdf2c9cca93816f0f3b2ab34c62c586fc507b4900a34cf9d0517e0fe10a89d154c5419c1f5e38de00e8834fe3dc1032abdeb10729a81655a69a12856a78ca6e12110580de879b086fd6608726541cfa9616326bdd36064bc0d1e5f9c93b41278bff6a13b2494b81e238c0c45aea1b07d855e8f3fe1478e373bd9d3957cf8a5e5b9003386793d994c7c575cff2322e2428cbbaa4f47560316ae3354a7478842ff7cc5dcbacb6e871e72b36f06d63a9aaeb9044cfb7974afdc238a5816f537dcf33ee40b4e1a5eb3cff2402b46d548264e133008d284f11b7e4e450bc3c5ff9f79b9c4", + "8df21892f5fc303b0de4adef1970186db6fe71bb3ea3094922e13afcfabf1d0be009f36d6f6310c5f9fda51f1a946507a055b645c296370440e5e83d8e906a2fb51f2b42de8856a81a4f28a73a8825c68ea08e5e366730bce8047011cb7d6d9be8c6f4211308fad21856284d5bc47d199988e0abf5badf8693ceeed0a2d98e8ae94b7775a42925edb1f697ffbd8e806af23145054a85e071819cca4cd48875290ca65e5ee72a9a54ff9f19c10ef4adaf8d04c9a9afcc73853fc128bbebc61f78702787c966ca6e1b1a0e4dab646acdfcd3c6bf3e5cfbec5ebe3e06c8abaa1de56e48421d87c46b5c78030afcafd91f27e7d7c85eb4872b", + "48ec6ec520f8e593d7b3f653eb15553de246723b81a6d0c3221aaa42a37420fba98a23796338dff5f845dce6d5a449be5ecc1887356619270461087e08d05fb60433a83d7bd00c002b09ea210b428965124b9b27d9105a71c826c1a2491cfd60e4cfa86c2da0c7100a8dc1c3f2f94b280d54e01e043acf0e966200d9fa8a41daf3b9382820786c75cadbb8841a1b2be5b6cbeb64878e4a231ae063a99b4e2308960ef0c8e2a16bb3545cc43bdf171493fb89a84f47e7973dc60cf75aeeca71e0a7ebe17d161d4fb9fe009941cc438f16a5bae6c99fcad08cac486eb2a48060b023d8730bf1d82fe60a2f036e6f52a5bff95f43bbe088933f", + "f4d84ed3e564c102600a795eaa9b1eaf4ad12f1a4deca1d042a0a2750ddf6201db03073d8bf553cb9dde48a1b0083827a609f7242b86584cc180964ae794b12ce55661e00e36a6ba4dbc389e6a5a85f1b45df9af7ead1b0a54db56e68639b9d438a91504e82c35d40c7bc7e048a53ac0b04accd0dadf4ac9884b0ca0e3cb5ba4336e3581be4c4760a553823ffa283a1120d4e145af56a59f2533903650f0b9e9ad9fe2e8a3c3c3dd03a1fcb709032c8835324839c735b0c051d0cbd8b5d867617c11023432e4bd275d3d0eb98a0b6cf58071a5b712922f2bc751ac7c2588c447444cde2f37a8ea5ec126425bf517e0d17c9e2999f52fee14b3", + "2ccea21bac9c2b70d3923309cbf2d7cb7abd1fcc8b8b002688870a80029c62397350c3c898194e5deea360bb963d26d485cb7963f8167586976ec0556950b2e86135f4a2800991ce8473bfd44a3c5e937a48b5e355ba5141bccf2131a83988d9d2a9e8e7635a956105b3512c05ef708139ced51d7a4e204c12d8a49a21e8dc6de2629a2fd092326885d9f218745fe09f6d91fb6afce250a30a63689534b6be1f26899ffa3767d835cf586aa47776700f94241bc999b1e3deefe188f37ff734f5f16ee6a00914323dc7b8a143c9137cdcc5cd08ae9566f04bb2941532674c97dff6ffa5ce3405ef8e5d27ec403114253dd6394c0167d72a0044c5", + "2b681c6398aee63bf862770341648bbcd31d7de7903c5903fe3d9469311320bb24d914f2af0cdca199c97214c7c679dc32a2800ba484a03c010ea6be3bb9f2c87e30a98b606050b8a3f297f12b8f92caaeceb3e844652115934874e0a1ab093a73d759b53f6a6c3096940dd22c2bb96ce6820a7b9c6d71a208de9892aa6a7209b0fff56a0cafea52b952cdd6f5752cff3309d448800b4e4c878aa595595b56b12b83fcd6ca89520c7da664e449d7b4438fc455888aad5de0fad9a06eed14afd3513b5ebbffe01775549b701181bd26370764f56eba52fdb24286ad1ac0f5418a7c429f7dfc7f3168437fa8eed7a2ed7c723a485e4c3ed14dea2e07", + "aadfd505a89f4aade2c3018258a7e039401b1fc6a7f3d87910dddbb880d372ec8a13c70d92245de5b8e5f9a285c33b99dc82fa2b22decee72b93a72211656ad7a52696c8e570f78be28c0e427a371dafde856e8d5ed24f83b0660b51e7fac05d93a8666dfde6def59af863f80f3e5f6801182c87422203df390dcb736b8f830052a8832eeeb0b4e27e732aaf793d166b5a3ec7745aeef3766937c2b75a276bddd145f6010c29d035e343e267cb2d828436876ec3a7ebe3b6347d4172f7a99d6821ce152e039e53deb33340b324c7f068ffb94b3cde35a8eaa12d15c3806a7ad0acec3e8c7078c1d32a28fd3eec9f32cb86e4c22166ff69e83785e851", + "1605b8cce529a9d6262fd4390d9e4ae5e14e0adc0ec89b028ef68dd0f373ea259aaa96f2967091dd0874c0105385e9e6da9ca68297c31afa44ef834535fb302ce5b4e49edacbbdf359fe1228a8172495b3e57014c27edd58b685110980056c50c398a64f4923f2d720b4df16d75cb36b4233660694182099c35028a972519c24764fc94e18e582b24deb3491535fc06b83837c7958522800e822201d694af0bd0aa3834e17d4b1ba36f470905ae5f8bbeeb6c4c8604d8af02baa347b07086d6989867ddd5e8e8ed7740c3469bfa2810519c55c6add1332c4c54ee9097961d6741cb12a09713a0d07645f784f42f5ad94b48b836b34263130b0483f15e3", + "ff9c6125b2f60bfd6c2427b279df070e430075096647599bdc68c531152c58e13858b82385d78c856092d6c74106e87ccf51ac7e673936332d9b223444eaa0e762ee258d8a733d3a515ec68ed73285e5ca183ae3278b4820b0ab2797feb1e7d8cc864df585dfb5ebe02a993325a9ad5e2d7d49d3132cf66013898351d044e0fe908ccdfeeebf651983601e3673a1f92d36510c0cc19b2e75856db8e4a41f92a51efa66d6cc22e414944c2c34a5a89ccde0be76f51410824e330d8e7c613194338c93732e8aea651fca18bcf1ac1824340c5553aff1e58d4ab8d7c8842b4712021e517cd6c140f6743c69c7bee05b10a8f24050a8caa4f96d1664909c5a06", + "6e85c2f8e1fdc3aaeb969da1258cb504bbf0070cd03d23b3fb5ee08feea5ee2e0ee1c71a5d0f4f701b351f4e4b4d74cb1e2ae6184814f77b62d2f08134b7236ebf6b67d8a6c9f01b4248b30667c555f5d8646dbfe291151b23c9c9857e33a4d5c847be29a5ee7b402e03bac02d1a4319acc0dd8f25e9c7a266f5e5c896cc11b5b238df96a0963ae806cb277abc515c298a3e61a3036b177acf87a56ca4478c4c6d0d468913de602ec891318bbaf52c97a77c35c5b7d164816cf24e4c4b0b5f45853882f716d61eb947a45ce2efa78f1c70a918512af1ad536cbe6148083385b34e207f5f690d7a954021e4b5f4258a385fd8a87809a481f34202af4caccb82", + "1e9b2c454e9de3a2d723d850331037dbf54133dbe27488ff757dd255833a27d8eb8a128ad12d0978b6884e25737086a704fb289aaaccf930d5b582ab4df1f55f0c429b6875edec3fe45464fa74164be056a55e243c4222c586bec5b18f39036aa903d98180f24f83d09a454dfa1e03a60e6a3ba4613e99c35f874d790174ee48a557f4f021ade4d1b278d7997ef094569b37b3db0505951e9ee8400adaea275c6db51b325ee730c69df97745b556ae41cd98741e28aa3a49544541eeb3da1b1e8fa4e8e9100d66dd0c7f5e2c271b1ecc077de79c462b9fe4c273543ecd82a5bea63c5acc01eca5fb780c7d7c8c9fe208ae8bd50cad1769693d92c6c8649d20d8", +} diff --git a/common/crypto/blake2b/blake2x.go b/common/crypto/blake2b/blake2x.go new file mode 100644 index 0000000..52c414d --- /dev/null +++ b/common/crypto/blake2b/blake2x.go @@ -0,0 +1,177 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "errors" + "io" +) + +// XOF defines the interface to hash functions that +// support arbitrary-length output. +type XOF interface { + // Write absorbs more data into the hash's state. It panics if called + // after Read. + io.Writer + + // Read reads more output from the hash. It returns io.EOF if the limit + // has been reached. + io.Reader + + // Clone returns a copy of the XOF in its current state. + Clone() XOF + + // Reset resets the XOF to its initial state. + Reset() +} + +// OutputLengthUnknown can be used as the size argument to NewXOF to indicate +// the length of the output is not known in advance. +const OutputLengthUnknown = 0 + +// magicUnknownOutputLength is a magic value for the output size that indicates +// an unknown number of output bytes. +const magicUnknownOutputLength = (1 << 32) - 1 + +// maxOutputLength is the absolute maximum number of bytes to produce when the +// number of output bytes is unknown. +const maxOutputLength = (1 << 32) * 64 + +// NewXOF creates a new variable-output-length hash. The hash either produce a +// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes +// (size == OutputLengthUnknown). In the latter case, an absolute limit of +// 256GiB applies. +// +// A non-nil key turns the hash into a MAC. The key must between +// zero and 32 bytes long. +func NewXOF(size uint32, key []byte) (XOF, error) { + if len(key) > Size { + return nil, errKeySize + } + if size == magicUnknownOutputLength { + // 2^32-1 indicates an unknown number of bytes and thus isn't a + // valid length. + return nil, errors.New("blake2b: XOF length too large") + } + if size == OutputLengthUnknown { + size = magicUnknownOutputLength + } + x := &xof{ + d: digest{ + size: Size, + keyLen: len(key), + }, + length: size, + } + copy(x.d.key[:], key) + x.Reset() + return x, nil +} + +type xof struct { + d digest + length uint32 + remaining uint64 + cfg, root, block [Size]byte + offset int + nodeOffset uint32 + readMode bool +} + +func (x *xof) Write(p []byte) (n int, err error) { + if x.readMode { + panic("blake2b: write to XOF after read") + } + return x.d.Write(p) +} + +func (x *xof) Clone() XOF { + clone := *x + return &clone +} + +func (x *xof) Reset() { + x.cfg[0] = byte(Size) + binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length + binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length + x.cfg[17] = byte(Size) // inner hash size + + x.d.Reset() + x.d.h[1] ^= uint64(x.length) << 32 + + x.remaining = uint64(x.length) + if x.remaining == magicUnknownOutputLength { + x.remaining = maxOutputLength + } + x.offset, x.nodeOffset = 0, 0 + x.readMode = false +} + +func (x *xof) Read(p []byte) (n int, err error) { + if !x.readMode { + x.d.finalize(&x.root) + x.readMode = true + } + + if x.remaining == 0 { + return 0, io.EOF + } + + n = len(p) + if uint64(n) > x.remaining { + n = int(x.remaining) + p = p[:n] + } + + if x.offset > 0 { + blockRemaining := Size - x.offset + if n < blockRemaining { + x.offset += copy(p, x.block[x.offset:]) + x.remaining -= uint64(n) + return + } + copy(p, x.block[x.offset:]) + p = p[blockRemaining:] + x.offset = 0 + x.remaining -= uint64(blockRemaining) + } + + for len(p) >= Size { + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + copy(p, x.block[:]) + p = p[Size:] + x.remaining -= uint64(Size) + } + + if todo := len(p); todo > 0 { + if x.remaining < uint64(Size) { + x.cfg[0] = byte(x.remaining) + } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + x.offset = copy(p, x.block[:todo]) + x.remaining -= uint64(todo) + } + return +} + +func (d *digest) initConfig(cfg *[Size]byte) { + d.offset, d.c[0], d.c[1] = 0, 0, 0 + for i := range d.h { + d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:]) + } +} diff --git a/common/crypto/blake2b/register.go b/common/crypto/blake2b/register.go new file mode 100644 index 0000000..d9fcac3 --- /dev/null +++ b/common/crypto/blake2b/register.go @@ -0,0 +1,32 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.9 + +package blake2b + +import ( + "crypto" + "hash" +) + +func init() { + newHash256 := func() hash.Hash { + h, _ := New256(nil) + return h + } + newHash384 := func() hash.Hash { + h, _ := New384(nil) + return h + } + + newHash512 := func() hash.Hash { + h, _ := New512(nil) + return h + } + + crypto.RegisterHash(crypto.BLAKE2b_256, newHash256) + crypto.RegisterHash(crypto.BLAKE2b_384, newHash384) + crypto.RegisterHash(crypto.BLAKE2b_512, newHash512) +} diff --git a/common/crypto/bls/bls.go b/common/crypto/bls/bls.go new file mode 100644 index 0000000..2c0b471 --- /dev/null +++ b/common/crypto/bls/bls.go @@ -0,0 +1,73 @@ +// Package bls implements a go-wrapper around a library implementing the +// the BLS12-381 curve and signature scheme. This package exposes a public API for +// verifying and aggregating BLS signatures used by Ethereum. +package bls + +import ( + "github.com/astranetworld/ast/common/crypto/bls/blst" + "github.com/astranetworld/ast/common/crypto/bls/common" +) + +// Initialize herumi temporarily while we transition to blst for ethdo. +func init() { +} + +// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice. +func SecretKeyFromBytes(privKey []byte) (SecretKey, error) { + return blst.SecretKeyFromBytes(privKey) +} + +// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice. +func PublicKeyFromBytes(pubKey []byte) (PublicKey, error) { + return blst.PublicKeyFromBytes(pubKey) +} + +// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice. +func SignatureFromBytes(sig []byte) (Signature, error) { + return blst.SignatureFromBytes(sig) +} + +// MultipleSignaturesFromBytes creates a slice of BLS signatures from a LittleEndian 2d-byte slice. +func MultipleSignaturesFromBytes(sigs [][]byte) ([]Signature, error) { + return blst.MultipleSignaturesFromBytes(sigs) +} + +// AggregatePublicKeys aggregates the provided raw public keys into a single key. +func AggregatePublicKeys(pubs [][]byte) (PublicKey, error) { + return blst.AggregatePublicKeys(pubs) +} + +// AggregateMultiplePubkeys aggregates the provided decompressed keys into a single key. +func AggregateMultiplePubkeys(pubs []PublicKey) PublicKey { + return blst.AggregateMultiplePubkeys(pubs) +} + +// AggregateSignatures converts a list of signatures into a single, aggregated sig. +func AggregateSignatures(sigs []common.Signature) common.Signature { + return blst.AggregateSignatures(sigs) +} + +// AggregateCompressedSignatures converts a list of compressed signatures into a single, aggregated sig. +func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) { + return blst.AggregateCompressedSignatures(multiSigs) +} + +// VerifyMultipleSignatures verifies multiple signatures for distinct messages securely. +func VerifyMultipleSignatures(sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) { + return blst.VerifyMultipleSignatures(sigs, msgs, pubKeys) +} + +// NewAggregateSignature creates a blank aggregate signature. +func NewAggregateSignature() common.Signature { + return blst.NewAggregateSignature() +} + +// RandKey creates a new private key using a random input. +func RandKey() (common.SecretKey, error) { + return blst.RandKey() +} + +// SecretKeyFromRandom32Byte creates a new private key using random 32 bytes +func SecretKeyFromRandom32Byte(ikm [32]byte) (common.SecretKey, error) { + return blst.SecretKeyFromRandom32Byte(ikm) +} diff --git a/common/crypto/bls/blst/aliases.go b/common/crypto/bls/blst/aliases.go new file mode 100644 index 0000000..e1e529b --- /dev/null +++ b/common/crypto/bls/blst/aliases.go @@ -0,0 +1,11 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import blst "github.com/supranational/blst/bindings/go" + +// Internal types for blst. +type blstPublicKey = blst.P1Affine +type blstSignature = blst.P2Affine +type blstAggregateSignature = blst.P2Aggregate +type blstAggregatePublicKey = blst.P1Aggregate diff --git a/common/crypto/bls/blst/doc.go b/common/crypto/bls/blst/doc.go new file mode 100644 index 0000000..76a2e9a --- /dev/null +++ b/common/crypto/bls/blst/doc.go @@ -0,0 +1,6 @@ +// Package blst implements a go-wrapper around a library implementing the +// the BLS12-381 curve and signature scheme. This package exposes a public API for +// verifying and aggregating BLS signatures used by Ethereum. +// +// This implementation uses the library written by Supranational, blst. +package blst diff --git a/common/crypto/bls/blst/init.go b/common/crypto/bls/blst/init.go new file mode 100644 index 0000000..02d1ac9 --- /dev/null +++ b/common/crypto/bls/blst/init.go @@ -0,0 +1,18 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import ( + "runtime" + + blst "github.com/supranational/blst/bindings/go" +) + +func init() { + // Reserve 1 core for general application work + maxProcs := runtime.GOMAXPROCS(0) - 1 + if maxProcs <= 0 { + maxProcs = 1 + } + blst.SetMaxProcs(maxProcs) +} diff --git a/common/crypto/bls/blst/public_key.go b/common/crypto/bls/blst/public_key.go new file mode 100644 index 0000000..4c2ec95 --- /dev/null +++ b/common/crypto/bls/blst/public_key.go @@ -0,0 +1,155 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import ( + "fmt" + + "github.com/astranetworld/ast/common/crypto/bls/common" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/log" + lru "github.com/hashicorp/golang-lru" + "github.com/pkg/errors" +) + +var maxKeys = 1000000 +var pubkeyCache, _ = lru.New(maxKeys) + +const ( + BLSPubkeyLength = 48 +) + +// PublicKey used in the BLS signature scheme. +type PublicKey struct { + p *blstPublicKey +} + +// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice. +func PublicKeyFromBytes(pubKey []byte) (common.PublicKey, error) { + if len(pubKey) != BLSPubkeyLength { + return nil, fmt.Errorf("public key must be %d bytes", BLSPubkeyLength) + } + newKey := (*[BLSPubkeyLength]byte)(pubKey) + if cv, ok := pubkeyCache.Get(*newKey); ok { + return cv.(*PublicKey).Copy(), nil + } + // Subgroup check NOT done when decompressing pubkey. + p := new(blstPublicKey).Uncompress(pubKey) + if p == nil { + return nil, errors.New("could not unmarshal bytes into public key") + } + // Subgroup and infinity check + if !p.KeyValidate() { + // NOTE: the error is not quite accurate since it includes group check + return nil, common.ErrInfinitePubKey + } + pubKeyObj := &PublicKey{p: p} + copiedKey := pubKeyObj.Copy() + cacheKey := *newKey + pubkeyCache.Add(cacheKey, copiedKey) + return pubKeyObj, nil +} + +// AggregatePublicKeys aggregates the provided raw public keys into a single key. +func AggregatePublicKeys(pubs [][]byte) (common.PublicKey, error) { + if len(pubs) == 0 { + return nil, errors.New("nil or empty public keys") + } + agg := new(blstAggregatePublicKey) + mulP1 := make([]*blstPublicKey, 0, len(pubs)) + for _, pubkey := range pubs { + pubKeyObj, err := PublicKeyFromBytes(pubkey) + if err != nil { + return nil, err + } + mulP1 = append(mulP1, pubKeyObj.(*PublicKey).p) + } + // No group check needed here since it is done in PublicKeyFromBytes + // Note the checks could be moved from PublicKeyFromBytes into Aggregate + // and take advantage of multi-threading. + agg.Aggregate(mulP1, false) + return &PublicKey{p: agg.ToAffine()}, nil +} + +// Marshal a public key into a LittleEndian byte slice. +func (p *PublicKey) Marshal() []byte { + return p.p.Compress() +} + +// Copy the public key to a new pointer reference. +func (p *PublicKey) Copy() common.PublicKey { + np := *p.p + return &PublicKey{p: &np} +} + +// IsInfinite checks if the public key is infinite. +func (p *PublicKey) IsInfinite() bool { + zeroKey := new(blstPublicKey) + return p.p.Equals(zeroKey) +} + +// Equals checks if the provided public key is equal to +// the current one. +func (p *PublicKey) Equals(p2 common.PublicKey) bool { + return p.p.Equals(p2.(*PublicKey).p) +} + +// Aggregate two public keys. +func (p *PublicKey) Aggregate(p2 common.PublicKey) common.PublicKey { + + agg := new(blstAggregatePublicKey) + // No group check here since it is checked at decompression time + agg.Add(p.p, false) + agg.Add(p2.(*PublicKey).p, false) + p.p = agg.ToAffine() + + return p +} + +func (p *PublicKey) UnmarshalJSON(input []byte) error { + log.Infof(string(input)) + log.Info("1111") + b := make([]byte, 0) + err := hexutil.UnmarshalFixedText("PublicKey", input, b[:]) + if err != nil { + return err + } + pubkey, err := PublicKeyFromBytes(b) + if err != nil { + p = pubkey.(*PublicKey) + } + return err +} + +func (p *PublicKey) UnmarshalText(input []byte) error { + log.Infof(string(input)) + log.Info("1111") + b := make([]byte, 0) + err := hexutil.UnmarshalFixedText("PublicKey", input, b[:]) + if err != nil { + return err + } + pubkey, err := PublicKeyFromBytes(b) + if err != nil { + p = pubkey.(*PublicKey) + } + return err +} + +func (p *PublicKey) MarshalText() ([]byte, error) { + return hexutil.Bytes(p.Marshal()).MarshalText() +} + +// AggregateMultiplePubkeys aggregates the provided decompressed keys into a single key. +func AggregateMultiplePubkeys(pubkeys []common.PublicKey) common.PublicKey { + mulP1 := make([]*blstPublicKey, 0, len(pubkeys)) + for _, pubkey := range pubkeys { + mulP1 = append(mulP1, pubkey.(*PublicKey).p) + } + agg := new(blstAggregatePublicKey) + // No group check needed here since it is done in PublicKeyFromBytes + // Note the checks could be moved from PublicKeyFromBytes into Aggregate + // and take advantage of multi-threading. + agg.Aggregate(mulP1, false) + return &PublicKey{p: agg.ToAffine()} +} diff --git a/common/crypto/bls/blst/secret_key.go b/common/crypto/bls/blst/secret_key.go new file mode 100644 index 0000000..92fd700 --- /dev/null +++ b/common/crypto/bls/blst/secret_key.go @@ -0,0 +1,96 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import ( + "crypto/subtle" + "fmt" + + "github.com/astranetworld/ast/common/crypto/bls/common" + "github.com/astranetworld/ast/common/crypto/rand" + + blst "github.com/supranational/blst/bindings/go" +) + +const BLSSecretKeyLength = 32 + +// bls12SecretKey used in the BLS signature scheme. +type bls12SecretKey struct { + p *blst.SecretKey +} + +// RandKey creates a new private key using a random method provided as an io.Reader. +func RandKey() (common.SecretKey, error) { + // Generate 32 bytes of randomness + var ikm [32]byte + _, err := rand.NewGenerator().Read(ikm[:]) + if err != nil { + return nil, err + } + // Defensive check, that we have not generated a secret key, + secKey := &bls12SecretKey{blst.KeyGen(ikm[:])} + if IsZero(secKey.Marshal()) { + return nil, common.ErrZeroKey + } + return secKey, nil +} + +// SecretKeyFromRandom32Byte creates a new private key using random 32 bytes +func SecretKeyFromRandom32Byte(ikm [32]byte) (common.SecretKey, error) { + // Defensive check, that we have not generated a secret key, + secKey := &bls12SecretKey{blst.KeyGen(ikm[:])} + if IsZero(secKey.Marshal()) { + return nil, common.ErrZeroKey + } + return secKey, nil +} + +// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice. +func SecretKeyFromBytes(privKey []byte) (common.SecretKey, error) { + if len(privKey) != BLSSecretKeyLength { + return nil, fmt.Errorf("secret key must be %d bytes", BLSSecretKeyLength) + } + secKey := new(blst.SecretKey).Deserialize(privKey) + if secKey == nil { + return nil, common.ErrSecretUnmarshal + } + wrappedKey := &bls12SecretKey{p: secKey} + if IsZero(privKey) { + return nil, common.ErrZeroKey + } + return wrappedKey, nil +} + +// PublicKey obtains the public key corresponding to the BLS secret key. +func (s *bls12SecretKey) PublicKey() common.PublicKey { + return &PublicKey{p: new(blstPublicKey).From(s.p)} +} + +// IsZero checks if the secret key is a zero key. +func IsZero(sKey []byte) bool { + b := byte(0) + for _, s := range sKey { + b |= s + } + return subtle.ConstantTimeByteEq(b, 0) == 1 +} + +// Sign a message using a secret key - in a beacon/validator client. +// +// In IETF draft BLS specification: +// Sign(SK, message) -> signature: a signing algorithm that generates +// +// a deterministic signature given a secret key SK and a message. +// +// In Ethereum proof of stake specification: +// def Sign(SK: int, message: Bytes) -> BLSSignature +func (s *bls12SecretKey) Sign(msg []byte) common.Signature { + signature := new(blstSignature).Sign(s.p, msg, dst) + return &Signature{s: signature} +} + +// Marshal a secret key into a LittleEndian byte slice. +func (s *bls12SecretKey) Marshal() []byte { + keyBytes := s.p.Serialize() + return keyBytes +} diff --git a/common/crypto/bls/blst/signature.go b/common/crypto/bls/blst/signature.go new file mode 100644 index 0000000..3973aef --- /dev/null +++ b/common/crypto/bls/blst/signature.go @@ -0,0 +1,262 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import ( + "bytes" + "fmt" + "sync" + + "github.com/astranetworld/ast/common/crypto/bls/common" + "github.com/astranetworld/ast/common/crypto/rand" + + "github.com/pkg/errors" + blst "github.com/supranational/blst/bindings/go" +) + +var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") + +const scalarBytes = 32 +const randBitsEntropy = 64 + +const BLSSignatureLength = 96 + +// Signature used in the BLS signature scheme. +type Signature struct { + s *blstSignature +} + +// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice. +func SignatureFromBytes(sig []byte) (common.Signature, error) { + if len(sig) != BLSSignatureLength { + return nil, fmt.Errorf("signature must be %d bytes", BLSSignatureLength) + } + signature := new(blstSignature).Uncompress(sig) + if signature == nil { + return nil, errors.New("could not unmarshal bytes into signature") + } + // Group check signature. Do not check for infinity since an aggregated signature + // could be infinite. + if !signature.SigValidate(false) { + return nil, errors.New("signature not in group") + } + return &Signature{s: signature}, nil +} + +// AggregateCompressedSignatures converts a list of compressed signatures into a single, aggregated sig. +func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) { + signature := new(blstAggregateSignature) + valid := signature.AggregateCompressed(multiSigs, true) + if !valid { + return nil, errors.New("provided signatures fail the group check and cannot be compressed") + } + return &Signature{s: signature.ToAffine()}, nil +} + +// MultipleSignaturesFromBytes creates a group of BLS signatures from a LittleEndian 2d-byte slice. +func MultipleSignaturesFromBytes(multiSigs [][]byte) ([]common.Signature, error) { + if len(multiSigs) == 0 { + return nil, fmt.Errorf("0 signatures provided to the method") + } + for _, s := range multiSigs { + if len(s) != BLSSignatureLength { + return nil, fmt.Errorf("signature must be %d bytes", BLSSignatureLength) + } + } + multiSignatures := new(blstSignature).BatchUncompress(multiSigs) + if len(multiSignatures) == 0 { + return nil, errors.New("could not unmarshal bytes into signature") + } + if len(multiSignatures) != len(multiSigs) { + return nil, errors.Errorf("wanted %d decompressed signatures but got %d", len(multiSigs), len(multiSignatures)) + } + wrappedSigs := make([]common.Signature, len(multiSignatures)) + for i, signature := range multiSignatures { + // Group check signature. Do not check for infinity since an aggregated signature + // could be infinite. + if !signature.SigValidate(false) { + return nil, errors.New("signature not in group") + } + copiedSig := signature + wrappedSigs[i] = &Signature{s: copiedSig} + } + return wrappedSigs, nil +} + +// Verify a bls signature given a public key, a message. +// +// In IETF draft BLS specification: +// Verify(PK, message, signature) -> VALID or INVALID: a verification +// +// algorithm that outputs VALID if signature is a valid signature of +// message under public key PK, and INVALID otherwise. +// +// In the Ethereum proof of stake specification: +// def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool +func (s *Signature) Verify(pubKey common.PublicKey, msg []byte) bool { + // Signature and PKs are assumed to have been validated upon decompression! + return s.s.Verify(false, pubKey.(*PublicKey).p, false, msg, dst) +} + +// AggregateVerify verifies each public key against its respective message. This is vulnerable to +// rogue public-key attack. Each user must provide a proof-of-knowledge of the public key. +// +// Note: The msgs must be distinct. For maximum performance, this method does not ensure distinct +// messages. +// +// In IETF draft BLS specification: +// AggregateVerify((PK_1, message_1), ..., (PK_n, message_n), +// +// signature) -> VALID or INVALID: an aggregate verification +// algorithm that outputs VALID if signature is a valid aggregated +// signature for a collection of public keys and messages, and +// outputs INVALID otherwise. +// +// In the Ethereum proof of stake specification: +// def AggregateVerify(pairs: Sequence[PK: BLSPubkey, message: Bytes], signature: BLSSignature) -> bool +// +// Deprecated: Use FastAggregateVerify or use this method in spectests only. +func (s *Signature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte) bool { + size := len(pubKeys) + if size == 0 { + return false + } + if size != len(msgs) { + return false + } + msgSlices := make([][]byte, len(msgs)) + rawKeys := make([]*blstPublicKey, len(msgs)) + for i := 0; i < size; i++ { + msgSlices[i] = msgs[i][:] + rawKeys[i] = pubKeys[i].(*PublicKey).p + } + // Signature and PKs are assumed to have been validated upon decompression! + return s.s.AggregateVerify(false, rawKeys, false, msgSlices, dst) +} + +// FastAggregateVerify verifies all the provided public keys with their aggregated signature. +// +// In IETF draft BLS specification: +// FastAggregateVerify(PK_1, ..., PK_n, message, signature) -> VALID +// +// or INVALID: a verification algorithm for the aggregate of multiple +// signatures on the same message. This function is faster than +// AggregateVerify. +// +// In the Ethereum proof of stake specification: +// def FastAggregateVerify(PKs: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool +func (s *Signature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { + if len(pubKeys) == 0 { + return false + } + rawKeys := make([]*blstPublicKey, len(pubKeys)) + for i := 0; i < len(pubKeys); i++ { + rawKeys[i] = pubKeys[i].(*PublicKey).p + } + return s.s.FastAggregateVerify(true, rawKeys, msg[:], dst) +} + +// Eth2FastAggregateVerify implements a wrapper on top of bls's FastAggregateVerify. It accepts G2_POINT_AT_INFINITY signature +// when pubkeys empty. +// +// Spec code: +// def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: +// +// """ +// Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. +// """ +// if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: +// return True +// return bls.FastAggregateVerify(pubkeys, message, signature) +func (s *Signature) Eth2FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { + if len(pubKeys) == 0 && bytes.Equal(s.Marshal(), common.InfiniteSignature[:]) { + return true + } + return s.FastAggregateVerify(pubKeys, msg) +} + +// NewAggregateSignature creates a blank aggregate signature. +func NewAggregateSignature() common.Signature { + sig := blst.HashToG2([]byte{'m', 'o', 'c', 'k'}, dst).ToAffine() + return &Signature{s: sig} +} + +// AggregateSignatures converts a list of signatures into a single, aggregated sig. +func AggregateSignatures(sigs []common.Signature) common.Signature { + if len(sigs) == 0 { + return nil + } + + rawSigs := make([]*blstSignature, len(sigs)) + for i := 0; i < len(sigs); i++ { + rawSigs[i] = sigs[i].(*Signature).s + } + + // Signature and PKs are assumed to have been validated upon decompression! + signature := new(blstAggregateSignature) + signature.Aggregate(rawSigs, false) + return &Signature{s: signature.ToAffine()} +} + +// VerifyMultipleSignatures verifies a non-singular set of signatures and its respective pubkeys and messages. +// This method provides a safe way to verify multiple signatures at once. We pick a number randomly from 1 to max +// uint64 and then multiply the signature by it. We continue doing this for all signatures and its respective pubkeys. +// S* = S_1 * r_1 + S_2 * r_2 + ... + S_n * r_n +// P'_{i,j} = P_{i,j} * r_i +// e(S*, G) = \prod_{i=1}^n \prod_{j=1}^{m_i} e(P'_{i,j}, M_{i,j}) +// Using this we can verify multiple signatures safely. +func VerifyMultipleSignatures(sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) { + if len(sigs) == 0 || len(pubKeys) == 0 { + return false, nil + } + rawSigs := new(blstSignature).BatchUncompress(sigs) + + length := len(sigs) + if length != len(pubKeys) || length != len(msgs) { + return false, errors.Errorf("provided signatures, pubkeys and messages have differing lengths. S: %d, P: %d,M %d", + length, len(pubKeys), len(msgs)) + } + mulP1Aff := make([]*blstPublicKey, length) + rawMsgs := make([]blst.Message, length) + + for i := 0; i < length; i++ { + mulP1Aff[i] = pubKeys[i].(*PublicKey).p + rawMsgs[i] = msgs[i][:] + } + // Secure source of RNG + randGen := rand.NewGenerator() + randLock := new(sync.Mutex) + + randFunc := func(scalar *blst.Scalar) { + var rbytes [scalarBytes]byte + randLock.Lock() + randGen.Read(rbytes[:]) // #nosec G104 -- Error will always be nil in `read` in math/rand + randLock.Unlock() + // Protect against the generator returning 0. Since the scalar value is + // derived from a big endian byte slice, we take the last byte. + rbytes[len(rbytes)-1] |= 0x01 + scalar.FromBEndian(rbytes[:]) + } + dummySig := new(blstSignature) + + // Validate signatures since we uncompress them here. Public keys should already be validated. + return dummySig.MultipleAggregateVerify(rawSigs, true, mulP1Aff, false, rawMsgs, dst, randFunc, randBitsEntropy), nil +} + +// Marshal a signature into a LittleEndian byte slice. +func (s *Signature) Marshal() []byte { + return s.s.Compress() +} + +// Copy returns a full deep copy of a signature. +func (s *Signature) Copy() common.Signature { + sign := *s.s + return &Signature{s: &sign} +} + +// VerifyCompressed verifies that the compressed signature and pubkey +// are valid from the message provided. +func VerifyCompressed(signature, pub, msg []byte) bool { + // Validate signature and PKs since we will uncompress them here + return new(blstSignature).VerifyCompressed(signature, true, pub, true, msg, dst) +} diff --git a/common/crypto/bls/blst/stub.go b/common/crypto/bls/blst/stub.go new file mode 100644 index 0000000..5e14ea0 --- /dev/null +++ b/common/crypto/bls/blst/stub.go @@ -0,0 +1,150 @@ +//go:build blst_disabled + +package blst + +// This stub file exists until build issues can be resolved for libfuzz. +const err = "blst is only supported on linux,darwin,windows" + +// SecretKey -- stub +type SecretKey struct{} + +// PublicKey -- stub +func (s SecretKey) PublicKey() common.PublicKey { + panic(err) +} + +// Sign -- stub +func (s SecretKey) Sign(_ []byte) common.Signature { + panic(err) +} + +// Marshal -- stub +func (s SecretKey) Marshal() []byte { + panic(err) +} + +// IsZero -- stub +func (s SecretKey) IsZero() bool { + panic(err) +} + +// PublicKey -- stub +type PublicKey struct{} + +// Marshal -- stub +func (p PublicKey) Marshal() []byte { + panic(err) +} + +// Copy -- stub +func (p PublicKey) Copy() common.PublicKey { + panic(err) +} + +// Aggregate -- stub +func (p PublicKey) Aggregate(_ common.PublicKey) common.PublicKey { + panic(err) +} + +// IsInfinite -- stub +func (p PublicKey) IsInfinite() bool { + panic(err) +} + +// Equals -- stub +func (p PublicKey) Equals(_ common.PublicKey) bool { + panic(err) +} + +// Signature -- stub +type Signature struct{} + +// Verify -- stub +func (s Signature) Verify(_ common.PublicKey, _ []byte) bool { + panic(err) +} + +// AggregateVerify -- stub +func (s Signature) AggregateVerify(_ []common.PublicKey, _ [][32]byte) bool { + panic(err) +} + +// FastAggregateVerify -- stub +func (s Signature) FastAggregateVerify(_ []common.PublicKey, _ [32]byte) bool { + panic(err) +} + +// Eth2FastAggregateVerify -- stub +func (s Signature) Eth2FastAggregateVerify(_ []common.PublicKey, _ [32]byte) bool { + panic(err) +} + +// Marshal -- stub +func (s Signature) Marshal() []byte { + panic(err) +} + +// Copy -- stub +func (s Signature) Copy() common.Signature { + panic(err) +} + +// SecretKeyFromBytes -- stub +func SecretKeyFromBytes(_ []byte) (SecretKey, error) { + panic(err) +} + +// PublicKeyFromBytes -- stub +func PublicKeyFromBytes(_ []byte) (PublicKey, error) { + panic(err) +} + +// SignatureFromBytes -- stub +func SignatureFromBytes(_ []byte) (Signature, error) { + panic(err) +} + +// MultipleSignaturesFromBytes -- stub +func MultipleSignaturesFromBytes(multiSigs [][]byte) ([]common.Signature, error) { + panic(err) +} + +// AggregatePublicKeys -- stub +func AggregatePublicKeys(_ [][]byte) (PublicKey, error) { + panic(err) +} + +// AggregateSignatures -- stub +func AggregateSignatures(_ []common.Signature) common.Signature { + panic(err) +} + +// AggregateMultiplePubkeys -- stub +func AggregateMultiplePubkeys(pubs []common.PublicKey) common.PublicKey { + panic(err) +} + +// AggregateCompressedSignatures -- stub +func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) { + panic(err) +} + +// VerifyMultipleSignatures -- stub +func VerifyMultipleSignatures(_ [][]byte, _ [][32]byte, _ []common.PublicKey) (bool, error) { + panic(err) +} + +// NewAggregateSignature -- stub +func NewAggregateSignature() common.Signature { + panic(err) +} + +// RandKey -- stub +func RandKey() (common.SecretKey, error) { + panic(err) +} + +// VerifyCompressed -- stub +func VerifyCompressed(_, _, _ []byte) bool { + panic(err) +} diff --git a/common/crypto/bls/common/constants.go b/common/crypto/bls/common/constants.go new file mode 100644 index 0000000..0959632 --- /dev/null +++ b/common/crypto/bls/common/constants.go @@ -0,0 +1,14 @@ +package common + +const ( + BLSPubkeyLength = 48 +) + +// ZeroSecretKey represents a zero secret key. +var ZeroSecretKey = [32]byte{} + +// InfinitePublicKey represents an infinite public key (G1 Point at Infinity). +var InfinitePublicKey = [BLSPubkeyLength]byte{0xC0} + +// InfiniteSignature represents an infinite signature (G2 Point at Infinity). +var InfiniteSignature = [96]byte{0xC0} diff --git a/common/crypto/bls/common/error.go b/common/crypto/bls/common/error.go new file mode 100644 index 0000000..13cc48e --- /dev/null +++ b/common/crypto/bls/common/error.go @@ -0,0 +1,13 @@ +package common + +import "errors" + +// ErrZeroKey describes an error due to a zero secret key. +var ErrZeroKey = errors.New("received secret key is zero") + +// ErrSecretUnmarshal describes an error which happens during unmarshalling +// a secret key. +var ErrSecretUnmarshal = errors.New("could not unmarshal bytes into secret key") + +// ErrInfinitePubKey describes an error due to an infinite public key. +var ErrInfinitePubKey = errors.New("received an infinite public key") diff --git a/common/crypto/bls/common/interface.go b/common/crypto/bls/common/interface.go new file mode 100644 index 0000000..64bba43 --- /dev/null +++ b/common/crypto/bls/common/interface.go @@ -0,0 +1,37 @@ +// Package common provides the BLS interfaces that are implemented by the various BLS wrappers. +// +// This package should not be used by downstream consumers. These interfaces are re-exporter by +// github.com/prysmaticlabs/prysm/crypto/bls. This package exists to prevent an import circular +// dependency. +package common + +// SecretKey represents a BLS secret or private key. +type SecretKey interface { + PublicKey() PublicKey + Sign(msg []byte) Signature + Marshal() []byte +} + +// PublicKey represents a BLS public key. +type PublicKey interface { + Marshal() []byte + Copy() PublicKey + Aggregate(p2 PublicKey) PublicKey + IsInfinite() bool + Equals(p2 PublicKey) bool + //json + UnmarshalJSON(input []byte) error + //UnmarshalText(input []byte) error + MarshalText() ([]byte, error) +} + +// Signature represents a BLS signature. +type Signature interface { + Verify(pubKey PublicKey, msg []byte) bool + // Deprecated: Use FastAggregateVerify or use this method in spectests only. + AggregateVerify(pubKeys []PublicKey, msgs [][32]byte) bool + FastAggregateVerify(pubKeys []PublicKey, msg [32]byte) bool + Eth2FastAggregateVerify(pubKeys []PublicKey, msg [32]byte) bool + Marshal() []byte + Copy() Signature +} diff --git a/common/crypto/bls/constants.go b/common/crypto/bls/constants.go new file mode 100644 index 0000000..f66aa55 --- /dev/null +++ b/common/crypto/bls/constants.go @@ -0,0 +1,7 @@ +package bls + +// DomainByteLength length of domain byte array. +const DomainByteLength = 4 + +// CurveOrder for the BLS12-381 curve. +const CurveOrder = "52435875175126190479447740508185965837690552500527637822603658699938581184513" diff --git a/common/crypto/bls/error.go b/common/crypto/bls/error.go new file mode 100644 index 0000000..4875668 --- /dev/null +++ b/common/crypto/bls/error.go @@ -0,0 +1 @@ +package bls diff --git a/common/crypto/bls/interface.go b/common/crypto/bls/interface.go new file mode 100644 index 0000000..aa4725e --- /dev/null +++ b/common/crypto/bls/interface.go @@ -0,0 +1,12 @@ +package bls + +import "github.com/astranetworld/ast/common/crypto/bls/common" + +// PublicKey represents a BLS public key. +type PublicKey = common.PublicKey + +// SecretKey represents a BLS secret or private key. +type SecretKey = common.SecretKey + +// Signature represents a BLS signature. +type Signature = common.Signature diff --git a/common/crypto/bls/lbs_ntest.go b/common/crypto/bls/lbs_ntest.go new file mode 100644 index 0000000..68b154d --- /dev/null +++ b/common/crypto/bls/lbs_ntest.go @@ -0,0 +1,416 @@ +//go:build (android || (linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package bls + +import ( + "bytes" + "encoding/hex" + "errors" + "strings" + + "github.com/astranetworld/ast/common/crypto/bls/blst" + "github.com/astranetworld/ast/common/crypto/bls/common" + "github.com/stretchr/testify/assert" + //"github.com/stretchr/testify/assert" +) + +func E2P(err error) { + if err != nil { + panic(err) + } +} + +func TestSignVerify2() error { + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + msg := []byte("hello") + sig := priv.Sign(msg) + if !sig.Verify(pub, msg) { + return errors.New("Signature did not verify") + } + return nil +} + +func TestAggregateVerify2() error { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + msgs = append(msgs, msg) + } + aggSig := AggregateSignatures(sigs) + if !aggSig.AggregateVerify(pubkeys, msgs) { + return errors.New("Signature did not verify") + } + return nil +} + +func TestAggregateVerify_CompressedSignatures2() error { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + var sigBytes [][]byte + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + sigBytes = append(sigBytes, sig.Marshal()) + msgs = append(msgs, msg) + } + aggSig := AggregateSignatures(sigs) + if !aggSig.AggregateVerify(pubkeys, msgs) { + return errors.New("Signature did not verify") + } + + aggSig2, err := AggregateCompressedSignatures(sigBytes) + if err != nil { + return err + } + if bytes.Compare(aggSig.Marshal(), aggSig2.Marshal()) != 0 { + return errors.New("Signature did not match up") + } + return nil +} + +func TestFastAggregateVerify2() error { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + for i := 0; i < 100; i++ { + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + } + aggSig := AggregateSignatures(sigs) + if !aggSig.FastAggregateVerify(pubkeys, msg) { + return errors.New("Signature did not verify") + } + return nil +} + +func TestVerifyCompressed2() error { + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + msg := []byte("hello") + sig := priv.Sign(msg) + if !sig.Verify(pub, msg) { + return errors.New("Non compressed signature did not verify") + } + + if !blst.VerifyCompressed(sig.Marshal(), pub.Marshal(), msg) { + return errors.New("Compressed signatures and pubkeys did not verify") + } + return nil +} + +func TestMultipleSignatureVerification2() error { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([][]byte, 0, 100) + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + sig := priv.Sign(msg[:]).Marshal() + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + msgs = append(msgs, msg) + } + verify, err := VerifyMultipleSignatures(sigs, msgs, pubkeys) + if err != nil { + return err + } + if !verify { + return errors.New("Signature did not verify") + } + return nil +} + +func TestFastAggregateVerify_ReturnsFalseOnEmptyPubKeyList2() error { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + aggSig := NewAggregateSignature() + if aggSig.FastAggregateVerify(pubkeys, msg) { + return errors.New("Expected FastAggregateVerify to return false with empty input") + } + return nil +} + +func TestEth2FastAggregateVerify2() error { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + for i := 0; i < 100; i++ { + priv, err := RandKey() + if err != nil { + return err + } + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + } + aggSig := AggregateSignatures(sigs) + if !aggSig.Eth2FastAggregateVerify(pubkeys, msg) { + return errors.New("Signature did not verify") + } + return nil +} + +func TestEth2FastAggregateVerify_ReturnsFalseOnEmptyPubKeyList2() error { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + aggSig := NewAggregateSignature() + if aggSig.Eth2FastAggregateVerify(pubkeys, msg) { + return errors.New("Expected Eth2FastAggregateVerify to return false with empty input") + } + return nil +} + +func TestEth2FastAggregateVerify_ReturnsTrueOnG2PointAtInfinity2() error { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + g2PointAtInfinity := append([]byte{0xC0}, make([]byte, 95)...) + aggSig, err := SignatureFromBytes(g2PointAtInfinity) + if err != nil { + return err + } + if !aggSig.Eth2FastAggregateVerify(pubkeys, msg) { + return errors.New("Eth2FastAggregateVerify error") + } + return nil +} + +func TestSignatureFromBytes2() error { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + name: "Good", + input: []byte{0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + }, + } + + for _, test := range tests { + res, err := SignatureFromBytes(test.input) + if test.err != nil { + if err == nil { + return errors.New("No error returned") + } + //assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + if err != nil { + return err + } + if bytes.Compare(res.Marshal(), test.input) != 0 { + return errors.New("bytes.Compare(res.Marshal(), test.input)!=0") + } + } + } + return nil +} + +func TestMultipleSignatureFromBytes2() error { + tests := []struct { + name string + input [][]byte + err error + }{ + { + name: "Nil", + err: errors.New("0 signatures provided to the method"), + }, + { + name: "Empty", + input: [][]byte{}, + err: errors.New("0 signatures provided to the method"), + }, + { + name: "Short", + input: [][]byte{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: [][]byte{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: [][]byte{{0x8f, 0xc0, 0xb4, 0x9e, 0x2e, 0xac, 0x50, 0x86, 0xe2, 0xe2, 0xaa, 0xf, 0xdc, 0x54, 0x23, 0x51, 0x6, 0xd8, 0x29, 0xf5, 0xae, 0x3, 0x5d, 0xb8, 0x31, 0x4d, 0x26, 0x3, 0x48, 0x18, 0xb9, 0x1f, 0x6b, 0xd7, 0x86, 0xb4, 0xa2, 0x69, 0xc7, 0xe7, 0xf5, 0xc0, 0x93, 0x19, 0x6e, 0xfd, 0x33, 0xb8, 0x1, 0xe1, 0x1f, 0x4e, 0xb4, 0xb1, 0xa0, 0x1, 0x30, 0x48, 0x8a, 0x6c, 0x97, 0x29, 0xd6, 0xcb, 0x1c, 0x45, 0xef, 0x87, 0xba, 0x4f, 0xce, 0x22, 0x84, 0x48, 0xad, 0x16, 0xf7, 0x5c, 0xb2, 0xa8, 0x34, 0xb9, 0xee, 0xb8, 0xbf, 0xe5, 0x58, 0x2c, 0x44, 0x7b, 0x1f, 0x9c, 0x22, 0x26, 0x3a, 0x22}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + name: "Good", + input: [][]byte{ + {0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + {0xb7, 0x86, 0xe5, 0x7, 0x43, 0xe2, 0x53, 0x6c, 0x15, 0x51, 0x9c, 0x6, 0x2a, 0xa7, 0xe5, 0x12, 0xf9, 0xb7, 0x77, 0x93, 0x3f, 0x55, 0xb3, 0xaf, 0x38, 0xf7, 0x39, 0xe4, 0x84, 0x6d, 0x88, 0x44, 0x52, 0x77, 0x65, 0x42, 0x95, 0xd9, 0x79, 0x93, 0x7e, 0xc8, 0x12, 0x60, 0xe3, 0x24, 0xea, 0x8, 0x10, 0x52, 0xcd, 0xd2, 0x7f, 0x5d, 0x25, 0x3a, 0xa8, 0x9b, 0xb7, 0x65, 0xa9, 0x31, 0xea, 0x7c, 0x85, 0x13, 0x53, 0xc0, 0xa3, 0x88, 0xd1, 0xa5, 0x54, 0x85, 0x2, 0x2d, 0xf8, 0xa1, 0xd7, 0xc1, 0x60, 0x58, 0x93, 0xec, 0x7c, 0xf9, 0x33, 0x43, 0x4, 0x48, 0x40, 0x97, 0xef, 0x67, 0x2a, 0x27}, + {0xb2, 0x12, 0xd0, 0xec, 0x46, 0x76, 0x6b, 0x24, 0x71, 0x91, 0x2e, 0xa8, 0x53, 0x9a, 0x48, 0xa3, 0x78, 0x30, 0xc, 0xe8, 0xf0, 0x86, 0xa3, 0x68, 0xec, 0xe8, 0x96, 0x43, 0x34, 0xda, 0xf, 0xf4, 0x65, 0x48, 0xbb, 0xe0, 0x92, 0xa1, 0x8, 0x12, 0x18, 0x46, 0xe6, 0x4a, 0xd6, 0x92, 0x88, 0xe, 0x2, 0xf5, 0xf3, 0x2a, 0x96, 0xb1, 0x4, 0xf1, 0x11, 0xa9, 0x92, 0x79, 0x52, 0x0, 0x64, 0x34, 0xeb, 0x25, 0xe, 0xf4, 0x29, 0x6b, 0x39, 0x4e, 0x28, 0x78, 0xfe, 0x25, 0xa3, 0xc0, 0x88, 0x5a, 0x40, 0xfd, 0x71, 0x37, 0x63, 0x79, 0xcd, 0x6b, 0x56, 0xda, 0xee, 0x91, 0x26, 0x72, 0xfc, 0xbc}, + {0x8f, 0xc0, 0xb4, 0x9e, 0x2e, 0xac, 0x50, 0x86, 0xe2, 0xe2, 0xaa, 0xf, 0xdc, 0x54, 0x23, 0x51, 0x6, 0xd8, 0x29, 0xf5, 0xae, 0x3, 0x5d, 0xb8, 0x31, 0x4d, 0x26, 0x3, 0x48, 0x18, 0xb9, 0x1f, 0x6b, 0xd7, 0x86, 0xb4, 0xa2, 0x69, 0xc7, 0xe7, 0xf5, 0xc0, 0x93, 0x19, 0x6e, 0xfd, 0x33, 0xb8, 0x1, 0xe1, 0x1f, 0x4e, 0xb4, 0xb1, 0xa0, 0x1, 0x30, 0x48, 0x8a, 0x6c, 0x97, 0x29, 0xd6, 0xcb, 0x1c, 0x45, 0xef, 0x87, 0xba, 0x4f, 0xce, 0x22, 0x84, 0x48, 0xad, 0x16, 0xf7, 0x5c, 0xb2, 0xa8, 0x34, 0xb9, 0xee, 0xb8, 0xbf, 0xe5, 0x58, 0x2c, 0x44, 0x7b, 0x1f, 0x9c, 0x22, 0x26, 0x3a, 0x22}, + }, + }, + } + + for _, test := range tests { + res, err := MultipleSignaturesFromBytes(test.input) + if test.err != nil { + if err == nil { + return errors.New("No error returned") + } + if !strings.Contains(test.err.Error(), err.Error()) { + return errors.New("Unexpected error returned") + } + } else { + if err != nil { + return err + } + for i, s := range res { + if bytes.Compare(s.Marshal(), test.input[i]) != 0 { + return errors.New("329:bytes.Compare(s.Marshal(), test.input[i])!=0") + } + } + } + } + return nil +} + +func TestCopy2() error { + priv, err := RandKey() + if err != nil { + return err + } + + signatureA := priv.Sign([]byte("foo")) + signatureB := signatureA.Copy() + + if signatureA == signatureB { + return errors.New("signatureA equal to signatureB") + } + + signatureC := priv.Sign([]byte("bar")) + if assert.ObjectsAreEqual(signatureC, signatureB) { + return errors.New("assert.ObjectsAreEqual(signatureC, signatureB){") + } + return nil +} + +func TestSecretKeyFromBytes2() error { + b, _ := hex.DecodeString("5de474bbf3fee5dfda9047287f596c6e6c0271876305357e399fffba6a5f9a9f") + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Bad", + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + err: common.ErrSecretUnmarshal, + }, + { + name: "Good", + input: []byte{0x25, 0x29, 0x5f, 0x0d, 0x1d, 0x59, 0x2a, 0x90, 0xb3, 0x33, 0xe2, 0x6e, 0x85, 0x14, 0x97, 0x08, 0x20, 0x8e, 0x9f, 0x8e, 0x8b, 0xc1, 0x8f, 0x6c, 0x77, 0xbd, 0x62, 0xf8, 0xad, 0x7a, 0x68, 0x66}, + }, + { + name: "myTest", + input: b, + }, + } + + for _, test := range tests { + res, err := SecretKeyFromBytes(test.input) + if test.err != nil { + if err == nil { + return errors.New("No error returned") + } + if !strings.Contains(test.err.Error(), err.Error()) { + return errors.New("Unexpected error returned") + } + } else { + if err != nil { + return err + } + if bytes.Compare(res.Marshal(), test.input) != 0 { + return errors.New("bytes.Compare(res.Marshal(), test.input)!=0") + } + } + } + return nil +} diff --git a/common/crypto/bls/lbs_test.go b/common/crypto/bls/lbs_test.go new file mode 100644 index 0000000..3da3cc2 --- /dev/null +++ b/common/crypto/bls/lbs_test.go @@ -0,0 +1,332 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package bls + +import ( + "bytes" + "encoding/hex" + "errors" + "github.com/astranetworld/ast/common/crypto/bls/blst" + "github.com/astranetworld/ast/common/crypto/bls/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestSignVerify(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + msg := []byte("hello") + sig := priv.Sign(msg) + assert.Equal(t, true, sig.Verify(pub, msg), "Signature did not verify") +} + +func TestAggregateVerify(t *testing.T) { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + msgs = append(msgs, msg) + } + aggSig := AggregateSignatures(sigs) + assert.Equal(t, true, aggSig.AggregateVerify(pubkeys, msgs), "Signature did not verify") +} + +func TestAggregateVerify_CompressedSignatures(t *testing.T) { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + var sigBytes [][]byte + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + sigBytes = append(sigBytes, sig.Marshal()) + msgs = append(msgs, msg) + } + aggSig := AggregateSignatures(sigs) + assert.Equal(t, true, aggSig.AggregateVerify(pubkeys, msgs), "Signature did not verify") + + aggSig2, err := AggregateCompressedSignatures(sigBytes) + assert.NoError(t, err) + assert.Equal(t, aggSig.Marshal(), aggSig2.Marshal(), "Signature did not match up") +} + +func TestFastAggregateVerify(t *testing.T) { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + for i := 0; i < 100; i++ { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + } + aggSig := AggregateSignatures(sigs) + assert.Equal(t, true, aggSig.FastAggregateVerify(pubkeys, msg), "Signature did not verify") + +} + +func TestVerifyCompressed(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + msg := []byte("hello") + sig := priv.Sign(msg) + assert.Equal(t, true, sig.Verify(pub, msg), "Non compressed signature did not verify") + assert.Equal(t, true, blst.VerifyCompressed(sig.Marshal(), pub.Marshal(), msg), "Compressed signatures and pubkeys did not verify") +} + +func TestMultipleSignatureVerification(t *testing.T) { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([][]byte, 0, 100) + var msgs [][32]byte + for i := 0; i < 100; i++ { + msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)} + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + sig := priv.Sign(msg[:]).Marshal() + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + msgs = append(msgs, msg) + } + verify, err := VerifyMultipleSignatures(sigs, msgs, pubkeys) + assert.NoError(t, err, "Signature did not verify") + assert.Equal(t, true, verify, "Signature did not verify") +} + +func TestFastAggregateVerify_ReturnsFalseOnEmptyPubKeyList(t *testing.T) { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + aggSig := NewAggregateSignature() + assert.Equal(t, false, aggSig.FastAggregateVerify(pubkeys, msg), "Expected FastAggregateVerify to return false with empty input ") +} + +func TestEth2FastAggregateVerify(t *testing.T) { + pubkeys := make([]common.PublicKey, 0, 100) + sigs := make([]common.Signature, 0, 100) + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + for i := 0; i < 100; i++ { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + sig := priv.Sign(msg[:]) + pubkeys = append(pubkeys, pub) + sigs = append(sigs, sig) + } + aggSig := AggregateSignatures(sigs) + assert.Equal(t, true, aggSig.Eth2FastAggregateVerify(pubkeys, msg), "Signature did not verify") + +} + +func TestEth2FastAggregateVerify_ReturnsFalseOnEmptyPubKeyList(t *testing.T) { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + aggSig := NewAggregateSignature() + assert.Equal(t, false, aggSig.Eth2FastAggregateVerify(pubkeys, msg), "Expected Eth2FastAggregateVerify to return false with empty input ") +} + +func TestEth2FastAggregateVerify_ReturnsTrueOnG2PointAtInfinity(t *testing.T) { + var pubkeys []common.PublicKey + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + + g2PointAtInfinity := append([]byte{0xC0}, make([]byte, 95)...) + aggSig, err := SignatureFromBytes(g2PointAtInfinity) + require.NoError(t, err) + assert.Equal(t, true, aggSig.Eth2FastAggregateVerify(pubkeys, msg)) +} + +func TestSignatureFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + name: "Good", + input: []byte{0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := SignatureFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestMultipleSignatureFromBytes(t *testing.T) { + tests := []struct { + name string + input [][]byte + err error + }{ + { + name: "Nil", + err: errors.New("0 signatures provided to the method"), + }, + { + name: "Empty", + input: [][]byte{}, + err: errors.New("0 signatures provided to the method"), + }, + { + name: "Short", + input: [][]byte{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: [][]byte{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: [][]byte{{0x8f, 0xc0, 0xb4, 0x9e, 0x2e, 0xac, 0x50, 0x86, 0xe2, 0xe2, 0xaa, 0xf, 0xdc, 0x54, 0x23, 0x51, 0x6, 0xd8, 0x29, 0xf5, 0xae, 0x3, 0x5d, 0xb8, 0x31, 0x4d, 0x26, 0x3, 0x48, 0x18, 0xb9, 0x1f, 0x6b, 0xd7, 0x86, 0xb4, 0xa2, 0x69, 0xc7, 0xe7, 0xf5, 0xc0, 0x93, 0x19, 0x6e, 0xfd, 0x33, 0xb8, 0x1, 0xe1, 0x1f, 0x4e, 0xb4, 0xb1, 0xa0, 0x1, 0x30, 0x48, 0x8a, 0x6c, 0x97, 0x29, 0xd6, 0xcb, 0x1c, 0x45, 0xef, 0x87, 0xba, 0x4f, 0xce, 0x22, 0x84, 0x48, 0xad, 0x16, 0xf7, 0x5c, 0xb2, 0xa8, 0x34, 0xb9, 0xee, 0xb8, 0xbf, 0xe5, 0x58, 0x2c, 0x44, 0x7b, 0x1f, 0x9c, 0x22, 0x26, 0x3a, 0x22}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + name: "Good", + input: [][]byte{ + {0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + {0xb7, 0x86, 0xe5, 0x7, 0x43, 0xe2, 0x53, 0x6c, 0x15, 0x51, 0x9c, 0x6, 0x2a, 0xa7, 0xe5, 0x12, 0xf9, 0xb7, 0x77, 0x93, 0x3f, 0x55, 0xb3, 0xaf, 0x38, 0xf7, 0x39, 0xe4, 0x84, 0x6d, 0x88, 0x44, 0x52, 0x77, 0x65, 0x42, 0x95, 0xd9, 0x79, 0x93, 0x7e, 0xc8, 0x12, 0x60, 0xe3, 0x24, 0xea, 0x8, 0x10, 0x52, 0xcd, 0xd2, 0x7f, 0x5d, 0x25, 0x3a, 0xa8, 0x9b, 0xb7, 0x65, 0xa9, 0x31, 0xea, 0x7c, 0x85, 0x13, 0x53, 0xc0, 0xa3, 0x88, 0xd1, 0xa5, 0x54, 0x85, 0x2, 0x2d, 0xf8, 0xa1, 0xd7, 0xc1, 0x60, 0x58, 0x93, 0xec, 0x7c, 0xf9, 0x33, 0x43, 0x4, 0x48, 0x40, 0x97, 0xef, 0x67, 0x2a, 0x27}, + {0xb2, 0x12, 0xd0, 0xec, 0x46, 0x76, 0x6b, 0x24, 0x71, 0x91, 0x2e, 0xa8, 0x53, 0x9a, 0x48, 0xa3, 0x78, 0x30, 0xc, 0xe8, 0xf0, 0x86, 0xa3, 0x68, 0xec, 0xe8, 0x96, 0x43, 0x34, 0xda, 0xf, 0xf4, 0x65, 0x48, 0xbb, 0xe0, 0x92, 0xa1, 0x8, 0x12, 0x18, 0x46, 0xe6, 0x4a, 0xd6, 0x92, 0x88, 0xe, 0x2, 0xf5, 0xf3, 0x2a, 0x96, 0xb1, 0x4, 0xf1, 0x11, 0xa9, 0x92, 0x79, 0x52, 0x0, 0x64, 0x34, 0xeb, 0x25, 0xe, 0xf4, 0x29, 0x6b, 0x39, 0x4e, 0x28, 0x78, 0xfe, 0x25, 0xa3, 0xc0, 0x88, 0x5a, 0x40, 0xfd, 0x71, 0x37, 0x63, 0x79, 0xcd, 0x6b, 0x56, 0xda, 0xee, 0x91, 0x26, 0x72, 0xfc, 0xbc}, + {0x8f, 0xc0, 0xb4, 0x9e, 0x2e, 0xac, 0x50, 0x86, 0xe2, 0xe2, 0xaa, 0xf, 0xdc, 0x54, 0x23, 0x51, 0x6, 0xd8, 0x29, 0xf5, 0xae, 0x3, 0x5d, 0xb8, 0x31, 0x4d, 0x26, 0x3, 0x48, 0x18, 0xb9, 0x1f, 0x6b, 0xd7, 0x86, 0xb4, 0xa2, 0x69, 0xc7, 0xe7, 0xf5, 0xc0, 0x93, 0x19, 0x6e, 0xfd, 0x33, 0xb8, 0x1, 0xe1, 0x1f, 0x4e, 0xb4, 0xb1, 0xa0, 0x1, 0x30, 0x48, 0x8a, 0x6c, 0x97, 0x29, 0xd6, 0xcb, 0x1c, 0x45, 0xef, 0x87, 0xba, 0x4f, 0xce, 0x22, 0x84, 0x48, 0xad, 0x16, 0xf7, 0x5c, 0xb2, 0xa8, 0x34, 0xb9, 0xee, 0xb8, 0xbf, 0xe5, 0x58, 0x2c, 0x44, 0x7b, 0x1f, 0x9c, 0x22, 0x26, 0x3a, 0x22}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := MultipleSignaturesFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + for i, s := range res { + assert.Equal(t, 0, bytes.Compare(s.Marshal(), test.input[i])) + } + } + }) + } +} + +func TestCopy(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + + signatureA := priv.Sign([]byte("foo")) + signatureB := signatureA.Copy() + + if signatureA == signatureB { + t.Fatal("signatureA equal to signatureB") + } + + signatureC := priv.Sign([]byte("bar")) + assert.NotEqual(t, signatureC, signatureB) +} + +func TestSecretKeyFromBytes(t *testing.T) { + b, _ := hex.DecodeString("5de474bbf3fee5dfda9047287f596c6e6c0271876305357e399fffba6a5f9a9f") + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Bad", + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + err: common.ErrSecretUnmarshal, + }, + { + name: "Good", + input: []byte{0x25, 0x29, 0x5f, 0x0d, 0x1d, 0x59, 0x2a, 0x90, 0xb3, 0x33, 0xe2, 0x6e, 0x85, 0x14, 0x97, 0x08, 0x20, 0x8e, 0x9f, 0x8e, 0x8b, 0xc1, 0x8f, 0x6c, 0x77, 0xbd, 0x62, 0xf8, 0xad, 0x7a, 0x68, 0x66}, + }, + { + name: "myTest", + input: b, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := SecretKeyFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} diff --git a/common/crypto/bls/signature_batch.go b/common/crypto/bls/signature_batch.go new file mode 100644 index 0000000..be4d046 --- /dev/null +++ b/common/crypto/bls/signature_batch.go @@ -0,0 +1,148 @@ +package bls + +import "github.com/pkg/errors" + +// SignatureBatch refers to the defined set of +// signatures and its respective public keys and +// messages required to verify it. +type SignatureBatch struct { + Signatures [][]byte + PublicKeys []PublicKey + Messages [][32]byte +} + +// NewSet constructs an empty signature batch object. +func NewSet() *SignatureBatch { + return &SignatureBatch{ + Signatures: [][]byte{}, + PublicKeys: []PublicKey{}, + Messages: [][32]byte{}, + } +} + +// Join merges the provided signature batch to out current one. +func (s *SignatureBatch) Join(set *SignatureBatch) *SignatureBatch { + s.Signatures = append(s.Signatures, set.Signatures...) + s.PublicKeys = append(s.PublicKeys, set.PublicKeys...) + s.Messages = append(s.Messages, set.Messages...) + return s +} + +// Verify the current signature batch using the batch verify algorithm. +func (s *SignatureBatch) Verify() (bool, error) { + return VerifyMultipleSignatures(s.Signatures, s.Messages, s.PublicKeys) +} + +// Copy the attached signature batch and return it +// to the caller. +func (s *SignatureBatch) Copy() *SignatureBatch { + signatures := make([][]byte, len(s.Signatures)) + pubkeys := make([]PublicKey, len(s.PublicKeys)) + messages := make([][32]byte, len(s.Messages)) + for i := range s.Signatures { + sig := make([]byte, len(s.Signatures[i])) + copy(sig, s.Signatures[i]) + signatures[i] = sig + } + for i := range s.PublicKeys { + pubkeys[i] = s.PublicKeys[i].Copy() + } + for i := range s.Messages { + copy(messages[i][:], s.Messages[i][:]) + } + return &SignatureBatch{ + Signatures: signatures, + PublicKeys: pubkeys, + Messages: messages, + } +} + +// RemoveDuplicates removes duplicate signature sets from the signature batch. +func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) { + if len(s.Signatures) == 0 || len(s.PublicKeys) == 0 || len(s.Messages) == 0 { + return 0, s, nil + } + if len(s.Signatures) != len(s.PublicKeys) || len(s.Signatures) != len(s.Messages) { + return 0, s, errors.Errorf("mismatch number of signatures, publickeys and messages in signature batch. "+ + "Signatures %d, Public Keys %d , Messages %d", s.Signatures, s.PublicKeys, s.Messages) + } + sigMap := make(map[string]int) + duplicateSet := make(map[int]bool) + for i := 0; i < len(s.Signatures); i++ { + if sigIdx, ok := sigMap[string(s.Signatures[i])]; ok { + if s.PublicKeys[sigIdx].Equals(s.PublicKeys[i]) && + s.Messages[sigIdx] == s.Messages[i] { + duplicateSet[i] = true + continue + } + } + sigMap[string(s.Signatures[i])] = i + } + + sigs := s.Signatures[:0] + pubs := s.PublicKeys[:0] + msgs := s.Messages[:0] + + for i := 0; i < len(s.Signatures); i++ { + if duplicateSet[i] { + continue + } + sigs = append(sigs, s.Signatures[i]) + pubs = append(pubs, s.PublicKeys[i]) + msgs = append(msgs, s.Messages[i]) + } + + s.Signatures = sigs + s.PublicKeys = pubs + s.Messages = msgs + + return len(duplicateSet), s, nil +} + +// AggregateBatch aggregates common messages in the provided batch to +// reduce the number of pairings required when we finally verify the +// whole batch. +func (s *SignatureBatch) AggregateBatch() (*SignatureBatch, error) { + if len(s.Signatures) == 0 || len(s.PublicKeys) == 0 || len(s.Messages) == 0 { + return s, nil + } + if len(s.Signatures) != len(s.PublicKeys) || len(s.Signatures) != len(s.Messages) { + return s, errors.Errorf("mismatch number of signatures, publickeys and messages in signature batch. "+ + "Signatures %d, Public Keys %d , Messages %d", s.Signatures, s.PublicKeys, s.Messages) + } + msgMap := make(map[[32]byte]*SignatureBatch) + + for i := 0; i < len(s.Messages); i++ { + currMsg := s.Messages[i] + currBatch, ok := msgMap[currMsg] + if ok { + currBatch.Signatures = append(currBatch.Signatures, s.Signatures[i]) + currBatch.Messages = append(currBatch.Messages, s.Messages[i]) + currBatch.PublicKeys = append(currBatch.PublicKeys, s.PublicKeys[i]) + continue + } + currBatch = &SignatureBatch{ + Signatures: [][]byte{s.Signatures[i]}, + Messages: [][32]byte{s.Messages[i]}, + PublicKeys: []PublicKey{s.PublicKeys[i]}, + } + msgMap[currMsg] = currBatch + } + newSt := NewSet() + for rt, b := range msgMap { + if len(b.PublicKeys) > 1 { + aggPub := AggregateMultiplePubkeys(b.PublicKeys) + aggSig, err := AggregateCompressedSignatures(b.Signatures) + if err != nil { + return nil, err + } + copiedRt := rt + b.PublicKeys = []PublicKey{aggPub} + b.Signatures = [][]byte{aggSig.Marshal()} + b.Messages = [][32]byte{copiedRt} + } + newObj := *b + newSt = newSt.Join(&newObj) + } + return newSt, nil +} diff --git a/common/crypto/bls12381/arithmetic_decl.go b/common/crypto/bls12381/arithmetic_decl.go new file mode 100644 index 0000000..09e2ec6 --- /dev/null +++ b/common/crypto/bls12381/arithmetic_decl.go @@ -0,0 +1,83 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build (amd64 && blsasm) || (amd64 && blsadx) + +package bls12381 + +import ( + "golang.org/x/sys/cpu" +) + +func init() { + if !enableADX || !cpu.X86.HasADX || !cpu.X86.HasBMI2 { + mul = mulNoADX + } +} + +// Use ADX backend for default +var mul func(c, a, b *fe) = mulADX + +func square(c, a *fe) { + mul(c, a, a) +} + +func neg(c, a *fe) { + if a.isZero() { + c.set(a) + } else { + _neg(c, a) + } +} + +//go:noescape +func add(c, a, b *fe) + +//go:noescape +func addAssign(a, b *fe) + +//go:noescape +func ladd(c, a, b *fe) + +//go:noescape +func laddAssign(a, b *fe) + +//go:noescape +func double(c, a *fe) + +//go:noescape +func doubleAssign(a *fe) + +//go:noescape +func ldouble(c, a *fe) + +//go:noescape +func sub(c, a, b *fe) + +//go:noescape +func subAssign(a, b *fe) + +//go:noescape +func lsubAssign(a, b *fe) + +//go:noescape +func _neg(c, a *fe) + +//go:noescape +func mulNoADX(c, a, b *fe) + +//go:noescape +func mulADX(c, a, b *fe) diff --git a/common/crypto/bls12381/arithmetic_fallback.go b/common/crypto/bls12381/arithmetic_fallback.go new file mode 100644 index 0000000..b7774be --- /dev/null +++ b/common/crypto/bls12381/arithmetic_fallback.go @@ -0,0 +1,566 @@ +// Native go field arithmetic code is generated with 'goff' +// https://github.com/ConsenSys/goff +// Many function signature of field operations are renamed. + +// Copyright 2020 ConsenSys AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// field modulus q = +// +// 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +// Code generated by goff DO NOT EDIT +// goff version: v0.1.0 - build: 790f1f56eac432441e043abff8819eacddd1d668 +// fe are assumed to be in Montgomery form in all methods + +// /!\ WARNING /!\ +// this code has not been audited and is provided as-is. In particular, +// there is no security guarantees such as constant time implementation +// or side-channel attack resistance +// /!\ WARNING /!\ + +// Package bls (generated by goff) contains field arithmetics operations + +//go:build !amd64 || (!blsasm && !blsadx) + +package bls12381 + +import ( + "math/bits" +) + +func add(z, x, y *fe) { + var carry uint64 + + z[0], carry = bits.Add64(x[0], y[0], 0) + z[1], carry = bits.Add64(x[1], y[1], carry) + z[2], carry = bits.Add64(x[2], y[2], carry) + z[3], carry = bits.Add64(x[3], y[3], carry) + z[4], carry = bits.Add64(x[4], y[4], carry) + z[5], _ = bits.Add64(x[5], y[5], carry) + + // if z > q --> z -= q + // note: this is NOT constant time + if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { + var b uint64 + z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) + z[1], b = bits.Sub64(z[1], 2210141511517208575, b) + z[2], b = bits.Sub64(z[2], 7435674573564081700, b) + z[3], b = bits.Sub64(z[3], 7239337960414712511, b) + z[4], b = bits.Sub64(z[4], 5412103778470702295, b) + z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) + } +} + +func addAssign(x, y *fe) { + var carry uint64 + + x[0], carry = bits.Add64(x[0], y[0], 0) + x[1], carry = bits.Add64(x[1], y[1], carry) + x[2], carry = bits.Add64(x[2], y[2], carry) + x[3], carry = bits.Add64(x[3], y[3], carry) + x[4], carry = bits.Add64(x[4], y[4], carry) + x[5], _ = bits.Add64(x[5], y[5], carry) + + // if z > q --> z -= q + // note: this is NOT constant time + if !(x[5] < 1873798617647539866 || (x[5] == 1873798617647539866 && (x[4] < 5412103778470702295 || (x[4] == 5412103778470702295 && (x[3] < 7239337960414712511 || (x[3] == 7239337960414712511 && (x[2] < 7435674573564081700 || (x[2] == 7435674573564081700 && (x[1] < 2210141511517208575 || (x[1] == 2210141511517208575 && (x[0] < 13402431016077863595))))))))))) { + var b uint64 + x[0], b = bits.Sub64(x[0], 13402431016077863595, 0) + x[1], b = bits.Sub64(x[1], 2210141511517208575, b) + x[2], b = bits.Sub64(x[2], 7435674573564081700, b) + x[3], b = bits.Sub64(x[3], 7239337960414712511, b) + x[4], b = bits.Sub64(x[4], 5412103778470702295, b) + x[5], _ = bits.Sub64(x[5], 1873798617647539866, b) + } +} + +func ladd(z, x, y *fe) { + var carry uint64 + z[0], carry = bits.Add64(x[0], y[0], 0) + z[1], carry = bits.Add64(x[1], y[1], carry) + z[2], carry = bits.Add64(x[2], y[2], carry) + z[3], carry = bits.Add64(x[3], y[3], carry) + z[4], carry = bits.Add64(x[4], y[4], carry) + z[5], _ = bits.Add64(x[5], y[5], carry) +} + +func laddAssign(x, y *fe) { + var carry uint64 + x[0], carry = bits.Add64(x[0], y[0], 0) + x[1], carry = bits.Add64(x[1], y[1], carry) + x[2], carry = bits.Add64(x[2], y[2], carry) + x[3], carry = bits.Add64(x[3], y[3], carry) + x[4], carry = bits.Add64(x[4], y[4], carry) + x[5], _ = bits.Add64(x[5], y[5], carry) +} + +func double(z, x *fe) { + var carry uint64 + + z[0], carry = bits.Add64(x[0], x[0], 0) + z[1], carry = bits.Add64(x[1], x[1], carry) + z[2], carry = bits.Add64(x[2], x[2], carry) + z[3], carry = bits.Add64(x[3], x[3], carry) + z[4], carry = bits.Add64(x[4], x[4], carry) + z[5], _ = bits.Add64(x[5], x[5], carry) + + // if z > q --> z -= q + // note: this is NOT constant time + if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { + var b uint64 + z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) + z[1], b = bits.Sub64(z[1], 2210141511517208575, b) + z[2], b = bits.Sub64(z[2], 7435674573564081700, b) + z[3], b = bits.Sub64(z[3], 7239337960414712511, b) + z[4], b = bits.Sub64(z[4], 5412103778470702295, b) + z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) + } +} + +func doubleAssign(z *fe) { + var carry uint64 + + z[0], carry = bits.Add64(z[0], z[0], 0) + z[1], carry = bits.Add64(z[1], z[1], carry) + z[2], carry = bits.Add64(z[2], z[2], carry) + z[3], carry = bits.Add64(z[3], z[3], carry) + z[4], carry = bits.Add64(z[4], z[4], carry) + z[5], _ = bits.Add64(z[5], z[5], carry) + + // if z > q --> z -= q + // note: this is NOT constant time + if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { + var b uint64 + z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) + z[1], b = bits.Sub64(z[1], 2210141511517208575, b) + z[2], b = bits.Sub64(z[2], 7435674573564081700, b) + z[3], b = bits.Sub64(z[3], 7239337960414712511, b) + z[4], b = bits.Sub64(z[4], 5412103778470702295, b) + z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) + } +} + +func ldouble(z, x *fe) { + var carry uint64 + + z[0], carry = bits.Add64(x[0], x[0], 0) + z[1], carry = bits.Add64(x[1], x[1], carry) + z[2], carry = bits.Add64(x[2], x[2], carry) + z[3], carry = bits.Add64(x[3], x[3], carry) + z[4], carry = bits.Add64(x[4], x[4], carry) + z[5], _ = bits.Add64(x[5], x[5], carry) +} + +func sub(z, x, y *fe) { + var b uint64 + z[0], b = bits.Sub64(x[0], y[0], 0) + z[1], b = bits.Sub64(x[1], y[1], b) + z[2], b = bits.Sub64(x[2], y[2], b) + z[3], b = bits.Sub64(x[3], y[3], b) + z[4], b = bits.Sub64(x[4], y[4], b) + z[5], b = bits.Sub64(x[5], y[5], b) + if b != 0 { + var c uint64 + z[0], c = bits.Add64(z[0], 13402431016077863595, 0) + z[1], c = bits.Add64(z[1], 2210141511517208575, c) + z[2], c = bits.Add64(z[2], 7435674573564081700, c) + z[3], c = bits.Add64(z[3], 7239337960414712511, c) + z[4], c = bits.Add64(z[4], 5412103778470702295, c) + z[5], _ = bits.Add64(z[5], 1873798617647539866, c) + } +} + +func subAssign(z, x *fe) { + var b uint64 + z[0], b = bits.Sub64(z[0], x[0], 0) + z[1], b = bits.Sub64(z[1], x[1], b) + z[2], b = bits.Sub64(z[2], x[2], b) + z[3], b = bits.Sub64(z[3], x[3], b) + z[4], b = bits.Sub64(z[4], x[4], b) + z[5], b = bits.Sub64(z[5], x[5], b) + if b != 0 { + var c uint64 + z[0], c = bits.Add64(z[0], 13402431016077863595, 0) + z[1], c = bits.Add64(z[1], 2210141511517208575, c) + z[2], c = bits.Add64(z[2], 7435674573564081700, c) + z[3], c = bits.Add64(z[3], 7239337960414712511, c) + z[4], c = bits.Add64(z[4], 5412103778470702295, c) + z[5], _ = bits.Add64(z[5], 1873798617647539866, c) + } +} + +func lsubAssign(z, x *fe) { + var b uint64 + z[0], b = bits.Sub64(z[0], x[0], 0) + z[1], b = bits.Sub64(z[1], x[1], b) + z[2], b = bits.Sub64(z[2], x[2], b) + z[3], b = bits.Sub64(z[3], x[3], b) + z[4], b = bits.Sub64(z[4], x[4], b) + z[5], _ = bits.Sub64(z[5], x[5], b) +} + +func neg(z *fe, x *fe) { + if x.isZero() { + z.zero() + return + } + var borrow uint64 + z[0], borrow = bits.Sub64(13402431016077863595, x[0], 0) + z[1], borrow = bits.Sub64(2210141511517208575, x[1], borrow) + z[2], borrow = bits.Sub64(7435674573564081700, x[2], borrow) + z[3], borrow = bits.Sub64(7239337960414712511, x[3], borrow) + z[4], borrow = bits.Sub64(5412103778470702295, x[4], borrow) + z[5], _ = bits.Sub64(1873798617647539866, x[5], borrow) +} + +func mul(z, x, y *fe) { + var t [6]uint64 + var c [3]uint64 + { + // round 0 + v := x[0] + c[1], c[0] = bits.Mul64(v, y[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd1(v, y[1], c[1]) + c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd1(v, y[2], c[1]) + c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd1(v, y[3], c[1]) + c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd1(v, y[4], c[1]) + c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd1(v, y[5], c[1]) + t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + { + // round 1 + v := x[1] + c[1], c[0] = madd1(v, y[0], t[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd2(v, y[1], c[1], t[1]) + c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd2(v, y[2], c[1], t[2]) + c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd2(v, y[3], c[1], t[3]) + c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd2(v, y[4], c[1], t[4]) + c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd2(v, y[5], c[1], t[5]) + t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + { + // round 2 + v := x[2] + c[1], c[0] = madd1(v, y[0], t[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd2(v, y[1], c[1], t[1]) + c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd2(v, y[2], c[1], t[2]) + c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd2(v, y[3], c[1], t[3]) + c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd2(v, y[4], c[1], t[4]) + c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd2(v, y[5], c[1], t[5]) + t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + { + // round 3 + v := x[3] + c[1], c[0] = madd1(v, y[0], t[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd2(v, y[1], c[1], t[1]) + c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd2(v, y[2], c[1], t[2]) + c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd2(v, y[3], c[1], t[3]) + c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd2(v, y[4], c[1], t[4]) + c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd2(v, y[5], c[1], t[5]) + t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + { + // round 4 + v := x[4] + c[1], c[0] = madd1(v, y[0], t[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd2(v, y[1], c[1], t[1]) + c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd2(v, y[2], c[1], t[2]) + c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd2(v, y[3], c[1], t[3]) + c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd2(v, y[4], c[1], t[4]) + c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd2(v, y[5], c[1], t[5]) + t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + { + // round 5 + v := x[5] + c[1], c[0] = madd1(v, y[0], t[0]) + m := c[0] * 9940570264628428797 + c[2] = madd0(m, 13402431016077863595, c[0]) + c[1], c[0] = madd2(v, y[1], c[1], t[1]) + c[2], z[0] = madd2(m, 2210141511517208575, c[2], c[0]) + c[1], c[0] = madd2(v, y[2], c[1], t[2]) + c[2], z[1] = madd2(m, 7435674573564081700, c[2], c[0]) + c[1], c[0] = madd2(v, y[3], c[1], t[3]) + c[2], z[2] = madd2(m, 7239337960414712511, c[2], c[0]) + c[1], c[0] = madd2(v, y[4], c[1], t[4]) + c[2], z[3] = madd2(m, 5412103778470702295, c[2], c[0]) + c[1], c[0] = madd2(v, y[5], c[1], t[5]) + z[5], z[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) + } + + // if z > q --> z -= q + // note: this is NOT constant time + if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { + var b uint64 + z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) + z[1], b = bits.Sub64(z[1], 2210141511517208575, b) + z[2], b = bits.Sub64(z[2], 7435674573564081700, b) + z[3], b = bits.Sub64(z[3], 7239337960414712511, b) + z[4], b = bits.Sub64(z[4], 5412103778470702295, b) + z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) + } +} + +func square(z, x *fe) { + + var p [6]uint64 + + var u, v uint64 + { + // round 0 + u, p[0] = bits.Mul64(x[0], x[0]) + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + var t uint64 + t, u, v = madd1sb(x[0], x[1], u) + C, p[0] = madd2(m, 2210141511517208575, v, C) + t, u, v = madd1s(x[0], x[2], t, u) + C, p[1] = madd2(m, 7435674573564081700, v, C) + t, u, v = madd1s(x[0], x[3], t, u) + C, p[2] = madd2(m, 7239337960414712511, v, C) + t, u, v = madd1s(x[0], x[4], t, u) + C, p[3] = madd2(m, 5412103778470702295, v, C) + _, u, v = madd1s(x[0], x[5], t, u) + p[5], p[4] = madd3(m, 1873798617647539866, v, C, u) + } + { + // round 1 + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + u, v = madd1(x[1], x[1], p[1]) + C, p[0] = madd2(m, 2210141511517208575, v, C) + var t uint64 + t, u, v = madd2sb(x[1], x[2], p[2], u) + C, p[1] = madd2(m, 7435674573564081700, v, C) + t, u, v = madd2s(x[1], x[3], p[3], t, u) + C, p[2] = madd2(m, 7239337960414712511, v, C) + t, u, v = madd2s(x[1], x[4], p[4], t, u) + C, p[3] = madd2(m, 5412103778470702295, v, C) + _, u, v = madd2s(x[1], x[5], p[5], t, u) + p[5], p[4] = madd3(m, 1873798617647539866, v, C, u) + } + { + // round 2 + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + C, p[0] = madd2(m, 2210141511517208575, p[1], C) + u, v = madd1(x[2], x[2], p[2]) + C, p[1] = madd2(m, 7435674573564081700, v, C) + var t uint64 + t, u, v = madd2sb(x[2], x[3], p[3], u) + C, p[2] = madd2(m, 7239337960414712511, v, C) + t, u, v = madd2s(x[2], x[4], p[4], t, u) + C, p[3] = madd2(m, 5412103778470702295, v, C) + _, u, v = madd2s(x[2], x[5], p[5], t, u) + p[5], p[4] = madd3(m, 1873798617647539866, v, C, u) + } + { + // round 3 + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + C, p[0] = madd2(m, 2210141511517208575, p[1], C) + C, p[1] = madd2(m, 7435674573564081700, p[2], C) + u, v = madd1(x[3], x[3], p[3]) + C, p[2] = madd2(m, 7239337960414712511, v, C) + var t uint64 + t, u, v = madd2sb(x[3], x[4], p[4], u) + C, p[3] = madd2(m, 5412103778470702295, v, C) + _, u, v = madd2s(x[3], x[5], p[5], t, u) + p[5], p[4] = madd3(m, 1873798617647539866, v, C, u) + } + { + // round 4 + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + C, p[0] = madd2(m, 2210141511517208575, p[1], C) + C, p[1] = madd2(m, 7435674573564081700, p[2], C) + C, p[2] = madd2(m, 7239337960414712511, p[3], C) + u, v = madd1(x[4], x[4], p[4]) + C, p[3] = madd2(m, 5412103778470702295, v, C) + _, u, v = madd2sb(x[4], x[5], p[5], u) + p[5], p[4] = madd3(m, 1873798617647539866, v, C, u) + } + { + // round 5 + m := p[0] * 9940570264628428797 + C := madd0(m, 13402431016077863595, p[0]) + C, z[0] = madd2(m, 2210141511517208575, p[1], C) + C, z[1] = madd2(m, 7435674573564081700, p[2], C) + C, z[2] = madd2(m, 7239337960414712511, p[3], C) + C, z[3] = madd2(m, 5412103778470702295, p[4], C) + u, v = madd1(x[5], x[5], p[5]) + z[5], z[4] = madd3(m, 1873798617647539866, v, C, u) + } + + // if z > q --> z -= q + // note: this is NOT constant time + if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { + var b uint64 + z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) + z[1], b = bits.Sub64(z[1], 2210141511517208575, b) + z[2], b = bits.Sub64(z[2], 7435674573564081700, b) + z[3], b = bits.Sub64(z[3], 7239337960414712511, b) + z[4], b = bits.Sub64(z[4], 5412103778470702295, b) + z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) + } +} + +// arith.go +// Copyright 2020 ConsenSys AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by goff DO NOT EDIT + +func madd(a, b, t, u, v uint64) (uint64, uint64, uint64) { + var carry uint64 + hi, lo := bits.Mul64(a, b) + v, carry = bits.Add64(lo, v, 0) + u, carry = bits.Add64(hi, u, carry) + t, _ = bits.Add64(t, 0, carry) + return t, u, v +} + +// madd0 hi = a*b + c (discards lo bits) +func madd0(a, b, c uint64) (hi uint64) { + var carry, lo uint64 + hi, lo = bits.Mul64(a, b) + _, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd1 hi, lo = a*b + c +func madd1(a, b, c uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd2 hi, lo = a*b + c + d +func madd2(a, b, c, d uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd2s superhi, hi, lo = 2*a*b + c + d + e +func madd2s(a, b, c, d, e uint64) (superhi, hi, lo uint64) { + var carry, sum uint64 + + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, lo, 0) + hi, superhi = bits.Add64(hi, hi, carry) + + sum, carry = bits.Add64(c, e, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, sum, 0) + hi, _ = bits.Add64(hi, 0, carry) + hi, _ = bits.Add64(hi, 0, d) + return +} + +func madd1s(a, b, d, e uint64) (superhi, hi, lo uint64) { + var carry uint64 + + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, lo, 0) + hi, superhi = bits.Add64(hi, hi, carry) + lo, carry = bits.Add64(lo, e, 0) + hi, _ = bits.Add64(hi, 0, carry) + hi, _ = bits.Add64(hi, 0, d) + return +} + +func madd2sb(a, b, c, e uint64) (superhi, hi, lo uint64) { + var carry, sum uint64 + + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, lo, 0) + hi, superhi = bits.Add64(hi, hi, carry) + + sum, carry = bits.Add64(c, e, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, sum, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +func madd1sb(a, b, e uint64) (superhi, hi, lo uint64) { + var carry uint64 + + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, lo, 0) + hi, superhi = bits.Add64(hi, hi, carry) + lo, carry = bits.Add64(lo, e, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, e, carry) + return +} diff --git a/common/crypto/bls12381/arithmetic_x86.s b/common/crypto/bls12381/arithmetic_x86.s new file mode 100644 index 0000000..2cebbc4 --- /dev/null +++ b/common/crypto/bls12381/arithmetic_x86.s @@ -0,0 +1,2150 @@ +// +build amd64,blsasm amd64,blsadx + +#include "textflag.h" + +// addition w/ modular reduction +// a = (a + b) % p +TEXT ·addAssign(SB), NOSPLIT, $0-16 + // | + MOVQ a+0(FP), DI + MOVQ b+8(FP), SI + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + + // | + ADDQ (SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ 32(SI), R12 + ADCQ 40(SI), R13 + + // | + MOVQ R8, R14 + MOVQ R9, R15 + MOVQ R10, CX + MOVQ R11, DX + MOVQ R12, SI + MOVQ R13, BX + MOVQ $0xb9feffffffffaaab, AX + SUBQ AX, R14 + MOVQ $0x1eabfffeb153ffff, AX + SBBQ AX, R15 + MOVQ $0x6730d2a0f6b0f624, AX + SBBQ AX, CX + MOVQ $0x64774b84f38512bf, AX + SBBQ AX, DX + MOVQ $0x4b1ba7b6434bacd7, AX + SBBQ AX, SI + MOVQ $0x1a0111ea397fe69a, AX + SBBQ AX, BX + CMOVQCC R14, R8 + CMOVQCC R15, R9 + CMOVQCC CX, R10 + CMOVQCC DX, R11 + CMOVQCC SI, R12 + CMOVQCC BX, R13 + + // | + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET + +/* | end */ + + +// addition w/ modular reduction +// c = (a + b) % p +TEXT ·add(SB), NOSPLIT, $0-24 + // | + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + + // | + ADDQ (SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ 32(SI), R12 + ADCQ 40(SI), R13 + + // | + MOVQ R8, R14 + MOVQ R9, R15 + MOVQ R10, CX + MOVQ R11, DX + MOVQ R12, SI + MOVQ R13, BX + MOVQ $0xb9feffffffffaaab, DI + SUBQ DI, R14 + MOVQ $0x1eabfffeb153ffff, DI + SBBQ DI, R15 + MOVQ $0x6730d2a0f6b0f624, DI + SBBQ DI, CX + MOVQ $0x64774b84f38512bf, DI + SBBQ DI, DX + MOVQ $0x4b1ba7b6434bacd7, DI + SBBQ DI, SI + MOVQ $0x1a0111ea397fe69a, DI + SBBQ DI, BX + CMOVQCC R14, R8 + CMOVQCC R15, R9 + CMOVQCC CX, R10 + CMOVQCC DX, R11 + CMOVQCC SI, R12 + CMOVQCC BX, R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// addition w/o reduction check +// c = (a + b) +TEXT ·ladd(SB), NOSPLIT, $0-24 + // | + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + + // | + ADDQ (SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ 32(SI), R12 + ADCQ 40(SI), R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// addition w/o reduction check +// a = a + b +TEXT ·laddAssign(SB), NOSPLIT, $0-16 + // | + MOVQ a+0(FP), DI + MOVQ b+8(FP), SI + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + + // | + ADDQ (SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ 32(SI), R12 + ADCQ 40(SI), R13 + + // | + MOVQ a+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// subtraction w/ modular reduction +// c = (a - b) % p +TEXT ·sub(SB), NOSPLIT, $0-24 + // | + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + XORQ AX, AX + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + SUBQ (SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + SBBQ 32(SI), R12 + SBBQ 40(SI), R13 + + // | + MOVQ $0xb9feffffffffaaab, R14 + MOVQ $0x1eabfffeb153ffff, R15 + MOVQ $0x6730d2a0f6b0f624, CX + MOVQ $0x64774b84f38512bf, DX + MOVQ $0x4b1ba7b6434bacd7, SI + MOVQ $0x1a0111ea397fe69a, BX + CMOVQCC AX, R14 + CMOVQCC AX, R15 + CMOVQCC AX, CX + CMOVQCC AX, DX + CMOVQCC AX, SI + CMOVQCC AX, BX + ADDQ R14, R8 + ADCQ R15, R9 + ADCQ CX, R10 + ADCQ DX, R11 + ADCQ SI, R12 + ADCQ BX, R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// subtraction w/ modular reduction +// a = (a - b) % p +TEXT ·subAssign(SB), NOSPLIT, $0-16 + // | + MOVQ a+0(FP), DI + MOVQ b+8(FP), SI + XORQ AX, AX + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + SUBQ (SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + SBBQ 32(SI), R12 + SBBQ 40(SI), R13 + + // | + MOVQ $0xb9feffffffffaaab, R14 + MOVQ $0x1eabfffeb153ffff, R15 + MOVQ $0x6730d2a0f6b0f624, CX + MOVQ $0x64774b84f38512bf, DX + MOVQ $0x4b1ba7b6434bacd7, SI + MOVQ $0x1a0111ea397fe69a, BX + CMOVQCC AX, R14 + CMOVQCC AX, R15 + CMOVQCC AX, CX + CMOVQCC AX, DX + CMOVQCC AX, SI + CMOVQCC AX, BX + ADDQ R14, R8 + ADCQ R15, R9 + ADCQ CX, R10 + ADCQ DX, R11 + ADCQ SI, R12 + ADCQ BX, R13 + + // | + MOVQ a+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// subtraction w/o reduction check +// a = (a - b) +TEXT ·lsubAssign(SB), NOSPLIT, $0-16 + // | + MOVQ a+0(FP), DI + MOVQ b+8(FP), SI + + // | + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + SUBQ (SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + SBBQ 32(SI), R12 + SBBQ 40(SI), R13 + + // | + MOVQ a+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + +// doubling w/ reduction +// c = (2 * a) % p +TEXT ·double(SB), NOSPLIT, $0-16 + // | + MOVQ a+8(FP), DI + + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + ADDQ R8, R8 + ADCQ R9, R9 + ADCQ R10, R10 + ADCQ R11, R11 + ADCQ R12, R12 + ADCQ R13, R13 + + // | + MOVQ R8, R14 + MOVQ R9, R15 + MOVQ R10, CX + MOVQ R11, DX + MOVQ R12, SI + MOVQ R13, BX + MOVQ $0xb9feffffffffaaab, DI + SUBQ DI, R14 + MOVQ $0x1eabfffeb153ffff, DI + SBBQ DI, R15 + MOVQ $0x6730d2a0f6b0f624, DI + SBBQ DI, CX + MOVQ $0x64774b84f38512bf, DI + SBBQ DI, DX + MOVQ $0x4b1ba7b6434bacd7, DI + SBBQ DI, SI + MOVQ $0x1a0111ea397fe69a, DI + SBBQ DI, BX + CMOVQCC R14, R8 + CMOVQCC R15, R9 + CMOVQCC CX, R10 + CMOVQCC DX, R11 + CMOVQCC SI, R12 + CMOVQCC BX, R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// doubling w/ reduction +// a = (2 * a) % p +TEXT ·doubleAssign(SB), NOSPLIT, $0-8 + // | + MOVQ a+0(FP), DI + + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + ADDQ R8, R8 + ADCQ R9, R9 + ADCQ R10, R10 + ADCQ R11, R11 + ADCQ R12, R12 + ADCQ R13, R13 + + // | + MOVQ R8, R14 + MOVQ R9, R15 + MOVQ R10, CX + MOVQ R11, DX + MOVQ R12, SI + MOVQ R13, BX + MOVQ $0xb9feffffffffaaab, AX + SUBQ AX, R14 + MOVQ $0x1eabfffeb153ffff, AX + SBBQ AX, R15 + MOVQ $0x6730d2a0f6b0f624, AX + SBBQ AX, CX + MOVQ $0x64774b84f38512bf, AX + SBBQ AX, DX + MOVQ $0x4b1ba7b6434bacd7, AX + SBBQ AX, SI + MOVQ $0x1a0111ea397fe69a, AX + SBBQ AX, BX + CMOVQCC R14, R8 + CMOVQCC R15, R9 + CMOVQCC CX, R10 + CMOVQCC DX, R11 + CMOVQCC SI, R12 + CMOVQCC BX, R13 + + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// doubling w/o reduction +// c = 2 * a +TEXT ·ldouble(SB), NOSPLIT, $0-16 + // | + MOVQ a+8(FP), DI + + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + MOVQ 40(DI), R13 + + // | + ADDQ R8, R8 + ADCQ R9, R9 + ADCQ R10, R10 + ADCQ R11, R11 + ADCQ R12, R12 + ADCQ R13, R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + + RET +/* | end */ + + +TEXT ·_neg(SB), NOSPLIT, $0-16 + // | + MOVQ a+8(FP), DI + + // | + MOVQ $0xb9feffffffffaaab, R8 + MOVQ $0x1eabfffeb153ffff, R9 + MOVQ $0x6730d2a0f6b0f624, R10 + MOVQ $0x64774b84f38512bf, R11 + MOVQ $0x4b1ba7b6434bacd7, R12 + MOVQ $0x1a0111ea397fe69a, R13 + SUBQ (DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + SBBQ 32(DI), R12 + SBBQ 40(DI), R13 + + // | + MOVQ c+0(FP), DI + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + MOVQ R11, 24(DI) + MOVQ R12, 32(DI) + MOVQ R13, 40(DI) + RET +/* | end */ + + +// multiplication without using MULX/ADX +// c = a * b % p +TEXT ·mulNoADX(SB), NOSPLIT, $24-24 + // | + +/* inputs */ + + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + MOVQ $0x00, R9 + MOVQ $0x00, R10 + MOVQ $0x00, R11 + MOVQ $0x00, R12 + MOVQ $0x00, R13 + MOVQ $0x00, R14 + MOVQ $0x00, R15 + + // | + +/* i0 */ + + // | a0 @ CX + MOVQ (DI), CX + + // | a0 * b0 + MOVQ (SI), AX + MULQ CX + MOVQ AX, (SP) + MOVQ DX, R8 + + // | a0 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R8 + ADCQ DX, R9 + + // | a0 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R9 + ADCQ DX, R10 + + // | a0 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R10 + ADCQ DX, R11 + + // | a0 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R11 + ADCQ DX, R12 + + // | a0 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + + // | + +/* i1 */ + + // | a1 @ CX + MOVQ 8(DI), CX + MOVQ $0x00, BX + + // | a1 * b0 + MOVQ (SI), AX + MULQ CX + ADDQ AX, R8 + ADCQ DX, R9 + ADCQ $0x00, R10 + ADCQ $0x00, BX + MOVQ R8, 8(SP) + MOVQ $0x00, R8 + + // | a1 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R9 + ADCQ DX, R10 + ADCQ BX, R11 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a1 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R10 + ADCQ DX, R11 + ADCQ BX, R12 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a1 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R11 + ADCQ DX, R12 + ADCQ BX, R13 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a1 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + ADCQ BX, R14 + + // | a1 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R13 + ADCQ DX, R14 + + // | + +/* i2 */ + + // | a2 @ CX + MOVQ 16(DI), CX + MOVQ $0x00, BX + + // | a2 * b0 + MOVQ (SI), AX + MULQ CX + ADDQ AX, R9 + ADCQ DX, R10 + ADCQ $0x00, R11 + ADCQ $0x00, BX + MOVQ R9, 16(SP) + MOVQ $0x00, R9 + + // | a2 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R10 + ADCQ DX, R11 + ADCQ BX, R12 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a2 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R11 + ADCQ DX, R12 + ADCQ BX, R13 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a2 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + ADCQ BX, R14 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a2 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R13 + ADCQ DX, R14 + ADCQ BX, R15 + + // | a2 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R14 + ADCQ DX, R15 + + // | + +/* i3 */ + + // | a3 @ CX + MOVQ 24(DI), CX + MOVQ $0x00, BX + + // | a3 * b0 + MOVQ (SI), AX + MULQ CX + ADDQ AX, R10 + ADCQ DX, R11 + ADCQ $0x00, R12 + ADCQ $0x00, BX + + // | a3 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R11 + ADCQ DX, R12 + ADCQ BX, R13 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a3 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + ADCQ BX, R14 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a3 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R13 + ADCQ DX, R14 + ADCQ BX, R15 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a3 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R14 + ADCQ DX, R15 + ADCQ BX, R8 + + // | a3 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R15 + ADCQ DX, R8 + + // | + +/* i4 */ + + // | a4 @ CX + MOVQ 32(DI), CX + MOVQ $0x00, BX + + // | a4 * b0 + MOVQ (SI), AX + MULQ CX + ADDQ AX, R11 + ADCQ DX, R12 + ADCQ $0x00, R13 + ADCQ $0x00, BX + + // | a4 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + ADCQ BX, R14 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a4 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R13 + ADCQ DX, R14 + ADCQ BX, R15 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a4 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R14 + ADCQ DX, R15 + ADCQ BX, R8 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a4 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R15 + ADCQ DX, R8 + ADCQ BX, R9 + + // | a4 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R8 + ADCQ DX, R9 + + // | + +/* i5 */ + + // | a5 @ CX + MOVQ 40(DI), CX + MOVQ $0x00, BX + + // | a5 * b0 + MOVQ (SI), AX + MULQ CX + ADDQ AX, R12 + ADCQ DX, R13 + ADCQ $0x00, R14 + ADCQ $0x00, BX + + // | a5 * b1 + MOVQ 8(SI), AX + MULQ CX + ADDQ AX, R13 + ADCQ DX, R14 + ADCQ BX, R15 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a5 * b2 + MOVQ 16(SI), AX + MULQ CX + ADDQ AX, R14 + ADCQ DX, R15 + ADCQ BX, R8 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a5 * b3 + MOVQ 24(SI), AX + MULQ CX + ADDQ AX, R15 + ADCQ DX, R8 + ADCQ BX, R9 + MOVQ $0x00, BX + ADCQ $0x00, BX + + // | a5 * b4 + MOVQ 32(SI), AX + MULQ CX + ADDQ AX, R8 + ADCQ DX, R9 + ADCQ $0x00, BX + + // | a5 * b5 + MOVQ 40(SI), AX + MULQ CX + ADDQ AX, R9 + ADCQ DX, BX + + // | + +/* */ + + // | + // | W + // | 0 (SP) | 1 8(SP) | 2 16(SP) | 3 R10 | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 R9 | 11 BX + + + MOVQ (SP), CX + MOVQ 8(SP), DI + MOVQ 16(SP), SI + MOVQ BX, (SP) + MOVQ R9, 8(SP) + + // | + +/* montgomery reduction */ + + // | + +/* i0 */ + + // | + // | W + // | 0 CX | 1 DI | 2 SI | 3 R10 | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 8(SP) | 11 (SP) + + + // | | u0 = w0 * inp + MOVQ CX, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w0 @ CX + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, CX + ADCQ DX, BX + + // | j1 + + // | w1 @ DI + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, DI + ADCQ $0x00, DX + ADDQ BX, DI + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w2 @ SI + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, SI + ADCQ $0x00, DX + ADDQ BX, SI + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w3 @ R10 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R10 + ADCQ $0x00, DX + ADDQ BX, R10 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w4 @ R11 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R11 + ADCQ $0x00, DX + ADDQ BX, R11 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w5 @ R12 + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ $0x00, DX + ADDQ BX, R12 + + // | w6 @ R13 + ADCQ DX, R13 + ADCQ $0x00, CX + + // | + +/* i1 */ + + // | + // | W + // | 0 - | 1 DI | 2 SI | 3 R10 | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 8(SP) | 11 (SP) + + + // | | u1 = w1 * inp + MOVQ DI, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w1 @ DI + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, DI + ADCQ DX, BX + + // | j1 + + // | w2 @ SI + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, SI + ADCQ $0x00, DX + ADDQ BX, SI + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w3 @ R10 + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, R10 + ADCQ $0x00, DX + ADDQ BX, R10 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w4 @ R11 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R11 + ADCQ $0x00, DX + ADDQ BX, R11 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w5 @ R12 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ $0x00, DX + ADDQ BX, R12 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w6 @ R13 + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, R13 + ADCQ DX, CX + ADDQ BX, R13 + + // | w7 @ R14 + ADCQ CX, R14 + MOVQ $0x00, CX + ADCQ $0x00, CX + + // | + +/* i2 */ + + // | + // | W + // | 0 - | 1 - | 2 SI | 3 R10 | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 8(SP) | 11 (SP) + + + // | | u2 = w2 * inp + MOVQ SI, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w2 @ SI + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, SI + ADCQ DX, BX + + // | j1 + + // | w3 @ R10 + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, R10 + ADCQ $0x00, DX + ADDQ BX, R10 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w4 @ R11 + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, R11 + ADCQ $0x00, DX + ADDQ BX, R11 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w5 @ R12 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ $0x00, DX + ADDQ BX, R12 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w6 @ R13 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R13 + ADCQ $0x00, DX + ADDQ BX, R13 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w7 @ R14 + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, R14 + ADCQ DX, CX + ADDQ BX, R14 + + // | w8 @ R15 + ADCQ CX, R15 + MOVQ $0x00, CX + ADCQ $0x00, CX + + // | + +/* i3 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 R10 | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 8(SP) | 11 (SP) + + + // | | u3 = w3 * inp + MOVQ R10, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w3 @ R10 + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, R10 + ADCQ DX, BX + + // | j1 + + // | w4 @ R11 + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, R11 + ADCQ $0x00, DX + ADDQ BX, R11 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w5 @ R12 + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ $0x00, DX + ADDQ BX, R12 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w6 @ R13 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R13 + ADCQ $0x00, DX + ADDQ BX, R13 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w7 @ R14 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R14 + ADCQ $0x00, DX + ADDQ BX, R14 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w8 @ R15 + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, R15 + ADCQ DX, CX + ADDQ BX, R15 + + // | w9 @ R8 + ADCQ CX, R8 + MOVQ $0x00, CX + ADCQ $0x00, CX + + // | + +/* i4 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 - | 4 R11 | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 8(SP) | 11 (SP) + + + // | | u4 = w4 * inp + MOVQ R11, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w4 @ R11 + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, R11 + ADCQ DX, BX + + // | j1 + + // | w5 @ R12 + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ $0x00, DX + ADDQ BX, R12 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w6 @ R13 + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, R13 + ADCQ $0x00, DX + ADDQ BX, R13 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w7 @ R14 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R14 + ADCQ $0x00, DX + ADDQ BX, R14 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w8 @ R15 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R15 + ADCQ $0x00, DX + ADDQ BX, R15 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w9 @ R8 + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, R8 + ADCQ DX, CX + ADDQ BX, R8 + + // | move to idle register + MOVQ 8(SP), DI + + // | w10 @ DI + ADCQ CX, DI + MOVQ $0x00, CX + ADCQ $0x00, CX + + // | + +/* i5 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 - | 4 - | 5 R12 + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 DI | 11 (SP) + + + // | | u5 = w5 * inp + MOVQ R12, AX + MULQ ·inp+0(SB) + MOVQ AX, R9 + MOVQ $0x00, BX + + // | + +/* */ + + // | j0 + + // | w5 @ R12 + MOVQ ·modulus+0(SB), AX + MULQ R9 + ADDQ AX, R12 + ADCQ DX, BX + + // | j1 + + // | w6 @ R13 + MOVQ ·modulus+8(SB), AX + MULQ R9 + ADDQ AX, R13 + ADCQ $0x00, DX + ADDQ BX, R13 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j2 + + // | w7 @ R14 + MOVQ ·modulus+16(SB), AX + MULQ R9 + ADDQ AX, R14 + ADCQ $0x00, DX + ADDQ BX, R14 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j3 + + // | w8 @ R15 + MOVQ ·modulus+24(SB), AX + MULQ R9 + ADDQ AX, R15 + ADCQ $0x00, DX + ADDQ BX, R15 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j4 + + // | w9 @ R8 + MOVQ ·modulus+32(SB), AX + MULQ R9 + ADDQ AX, R8 + ADCQ $0x00, DX + ADDQ BX, R8 + MOVQ $0x00, BX + ADCQ DX, BX + + // | j5 + + // | w10 @ DI + MOVQ ·modulus+40(SB), AX + MULQ R9 + ADDQ AX, DI + ADCQ DX, CX + ADDQ BX, DI + + // | w11 @ CX + ADCQ (SP), CX + + // | + // | W montgomerry reduction ends + // | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - + // | 6 R13 | 7 R14 | 8 R15 | 9 R8 | 10 DI | 11 CX + + + // | + + +/* modular reduction */ + + MOVQ R13, R10 + SUBQ ·modulus+0(SB), R10 + MOVQ R14, R11 + SBBQ ·modulus+8(SB), R11 + MOVQ R15, R12 + SBBQ ·modulus+16(SB), R12 + MOVQ R8, AX + SBBQ ·modulus+24(SB), AX + MOVQ DI, BX + SBBQ ·modulus+32(SB), BX + MOVQ CX, R9 + SBBQ ·modulus+40(SB), R9 + // | + +/* out */ + + MOVQ c+0(FP), SI + CMOVQCC R10, R13 + MOVQ R13, (SI) + CMOVQCC R11, R14 + MOVQ R14, 8(SI) + CMOVQCC R12, R15 + MOVQ R15, 16(SI) + CMOVQCC AX, R8 + MOVQ R8, 24(SI) + CMOVQCC BX, DI + MOVQ DI, 32(SI) + CMOVQCC R9, CX + MOVQ CX, 40(SI) + RET + + // | + +/* end */ + + +// multiplication +// c = a * b % p +TEXT ·mulADX(SB), NOSPLIT, $16-24 + // | + +/* inputs */ + + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + XORQ AX, AX + + // | + +/* i0 */ + + // | a0 @ DX + MOVQ (DI), DX + + // | a0 * b0 + MULXQ (SI), AX, CX + MOVQ AX, (SP) + + // | a0 * b1 + MULXQ 8(SI), AX, R8 + ADCXQ AX, CX + + // | a0 * b2 + MULXQ 16(SI), AX, R9 + ADCXQ AX, R8 + + // | a0 * b3 + MULXQ 24(SI), AX, R10 + ADCXQ AX, R9 + + // | a0 * b4 + MULXQ 32(SI), AX, R11 + ADCXQ AX, R10 + + // | a0 * b5 + MULXQ 40(SI), AX, R12 + ADCXQ AX, R11 + ADCQ $0x00, R12 + + // | + +/* i1 */ + + // | a1 @ DX + MOVQ 8(DI), DX + XORQ R13, R13 + + // | a1 * b0 + MULXQ (SI), AX, BX + ADOXQ AX, CX + ADCXQ BX, R8 + MOVQ CX, 8(SP) + + // | a1 * b1 + MULXQ 8(SI), AX, BX + ADOXQ AX, R8 + ADCXQ BX, R9 + + // | a1 * b2 + MULXQ 16(SI), AX, BX + ADOXQ AX, R9 + ADCXQ BX, R10 + + // | a1 * b3 + MULXQ 24(SI), AX, BX + ADOXQ AX, R10 + ADCXQ BX, R11 + + // | a1 * b4 + MULXQ 32(SI), AX, BX + ADOXQ AX, R11 + ADCXQ BX, R12 + + // | a1 * b5 + MULXQ 40(SI), AX, BX + ADOXQ AX, R12 + ADOXQ R13, R13 + ADCXQ BX, R13 + + // | + +/* i2 */ + + // | a2 @ DX + MOVQ 16(DI), DX + XORQ R14, R14 + + // | a2 * b0 + MULXQ (SI), AX, BX + ADOXQ AX, R8 + ADCXQ BX, R9 + + // | a2 * b1 + MULXQ 8(SI), AX, BX + ADOXQ AX, R9 + ADCXQ BX, R10 + + // | a2 * b2 + MULXQ 16(SI), AX, BX + ADOXQ AX, R10 + ADCXQ BX, R11 + + // | a2 * b3 + MULXQ 24(SI), AX, BX + ADOXQ AX, R11 + ADCXQ BX, R12 + + // | a2 * b4 + MULXQ 32(SI), AX, BX + ADOXQ AX, R12 + ADCXQ BX, R13 + + // | a2 * b5 + MULXQ 40(SI), AX, BX + ADOXQ AX, R13 + ADOXQ R14, R14 + ADCXQ BX, R14 + + // | + +/* i3 */ + + // | a3 @ DX + MOVQ 24(DI), DX + XORQ R15, R15 + + // | a3 * b0 + MULXQ (SI), AX, BX + ADOXQ AX, R9 + ADCXQ BX, R10 + + // | a3 * b1 + MULXQ 8(SI), AX, BX + ADOXQ AX, R10 + ADCXQ BX, R11 + + // | a3 * b2 + MULXQ 16(SI), AX, BX + ADOXQ AX, R11 + ADCXQ BX, R12 + + // | a3 * b3 + MULXQ 24(SI), AX, BX + ADOXQ AX, R12 + ADCXQ BX, R13 + + // | a3 * b4 + MULXQ 32(SI), AX, BX + ADOXQ AX, R13 + ADCXQ BX, R14 + + // | a3 * b5 + MULXQ 40(SI), AX, BX + ADOXQ AX, R14 + ADOXQ R15, R15 + ADCXQ BX, R15 + + // | + +/* i4 */ + + // | a4 @ DX + MOVQ 32(DI), DX + XORQ CX, CX + + // | a4 * b0 + MULXQ (SI), AX, BX + ADOXQ AX, R10 + ADCXQ BX, R11 + + // | a4 * b1 + MULXQ 8(SI), AX, BX + ADOXQ AX, R11 + ADCXQ BX, R12 + + // | a4 * b2 + MULXQ 16(SI), AX, BX + ADOXQ AX, R12 + ADCXQ BX, R13 + + // | a4 * b3 + MULXQ 24(SI), AX, BX + ADOXQ AX, R13 + ADCXQ BX, R14 + + // | a4 * b4 + MULXQ 32(SI), AX, BX + ADOXQ AX, R14 + ADCXQ BX, R15 + + // | a4 * b5 + MULXQ 40(SI), AX, BX + ADOXQ AX, R15 + ADOXQ CX, CX + ADCXQ BX, CX + + // | + +/* i5 */ + + // | a5 @ DX + MOVQ 40(DI), DX + XORQ DI, DI + + // | a5 * b0 + MULXQ (SI), AX, BX + ADOXQ AX, R11 + ADCXQ BX, R12 + + // | a5 * b1 + MULXQ 8(SI), AX, BX + ADOXQ AX, R12 + ADCXQ BX, R13 + + // | a5 * b2 + MULXQ 16(SI), AX, BX + ADOXQ AX, R13 + ADCXQ BX, R14 + + // | a5 * b3 + MULXQ 24(SI), AX, BX + ADOXQ AX, R14 + ADCXQ BX, R15 + + // | a5 * b4 + MULXQ 32(SI), AX, BX + ADOXQ AX, R15 + ADCXQ BX, CX + + // | a5 * b5 + MULXQ 40(SI), AX, BX + ADOXQ AX, CX + ADOXQ BX, DI + ADCQ $0x00, DI + + // | + +/* */ + + // | + // | W + // | 0 (SP) | 1 8(SP) | 2 R8 | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 DI + + + MOVQ (SP), BX + MOVQ 8(SP), SI + MOVQ DI, (SP) + + // | + // | W ready to mont + // | 0 BX | 1 SI | 2 R8 | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | + +/* montgomery reduction */ + + // | clear flags + XORQ AX, AX + + // | + +/* i0 */ + + // | + // | W + // | 0 BX | 1 SI | 2 R8 | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u0 = w0 * inp + MOVQ BX, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w0 @ BX + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, BX + ADCXQ DI, SI + + // | j1 + + // | w1 @ SI + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, SI + ADCXQ DI, R8 + + // | j2 + + // | w2 @ R8 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R8 + ADCXQ DI, R9 + + // | j3 + + // | w3 @ R9 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R9 + ADCXQ DI, R10 + + // | j4 + + // | w4 @ R10 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R10 + ADCXQ DI, R11 + + // | j5 + + // | w5 @ R11 + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + ADOXQ BX, R12 + ADCXQ BX, BX + MOVQ $0x00, AX + ADOXQ AX, BX + + // | clear flags + XORQ AX, AX + + // | + +/* i1 */ + + // | + // | W + // | 0 - | 1 SI | 2 R8 | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u1 = w1 * inp + MOVQ SI, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w1 @ SI + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, SI + ADCXQ DI, R8 + + // | j1 + + // | w2 @ R8 + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, R8 + ADCXQ DI, R9 + + // | j2 + + // | w3 @ R9 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R9 + ADCXQ DI, R10 + + // | j3 + + // | w4 @ R10 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R10 + ADCXQ DI, R11 + + // | j4 + + // | w5 @ R11 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + + // | j5 + + // | w6 @ R12 + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, R12 + ADCXQ DI, R13 + ADOXQ BX, R13 + ADCXQ SI, SI + MOVQ $0x00, AX + ADOXQ AX, SI + + // | clear flags + XORQ AX, AX + + // | + +/* i2 */ + + // | + // | W + // | 0 - | 1 - | 2 R8 | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u2 = w2 * inp + MOVQ R8, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w2 @ R8 + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, R8 + ADCXQ DI, R9 + + // | j1 + + // | w3 @ R9 + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, R9 + ADCXQ DI, R10 + + // | j2 + + // | w4 @ R10 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R10 + ADCXQ DI, R11 + + // | j3 + + // | w5 @ R11 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + + // | j4 + + // | w6 @ R12 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R12 + ADCXQ DI, R13 + + // | j5 + + // | w7 @ R13 + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, R13 + ADCXQ DI, R14 + ADOXQ SI, R14 + ADCXQ R8, R8 + MOVQ $0x00, AX + ADOXQ AX, R8 + + // | clear flags + XORQ AX, AX + + // | + +/* i3 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 R9 | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u3 = w3 * inp + MOVQ R9, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w3 @ R9 + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, R9 + ADCXQ DI, R10 + + // | j1 + + // | w4 @ R10 + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, R10 + ADCXQ DI, R11 + + // | j2 + + // | w5 @ R11 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + + // | j3 + + // | w6 @ R12 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R12 + ADCXQ DI, R13 + + // | j4 + + // | w7 @ R13 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R13 + ADCXQ DI, R14 + + // | j5 + + // | w8 @ R14 + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, R14 + ADCXQ DI, R15 + ADOXQ R8, R15 + ADCXQ R9, R9 + MOVQ $0x00, AX + ADOXQ AX, R9 + + // | clear flags + XORQ AX, AX + + // | + +/* i4 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 - | 4 R10 | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u4 = w4 * inp + MOVQ R10, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w4 @ R10 + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, R10 + ADCXQ DI, R11 + + // | j1 + + // | w5 @ R11 + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + + // | j2 + + // | w6 @ R12 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R12 + ADCXQ DI, R13 + + // | j3 + + // | w7 @ R13 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R13 + ADCXQ DI, R14 + + // | j4 + + // | w8 @ R14 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R14 + ADCXQ DI, R15 + + // | j5 + + // | w9 @ R15 + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, R15 + ADCXQ DI, CX + ADOXQ R9, CX + ADCXQ R10, R10 + MOVQ $0x00, AX + ADOXQ AX, R10 + + // | clear flags + XORQ AX, AX + + // | + +/* i5 */ + + // | + // | W + // | 0 - | 1 - | 2 - | 3 - | 4 - | 5 R11 + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 (SP) + + + // | | u5 = w5 * inp + MOVQ R11, DX + MULXQ ·inp+0(SB), DX, DI + + // | + +/* */ + + // | j0 + + // | w5 @ R11 + MULXQ ·modulus+0(SB), AX, DI + ADOXQ AX, R11 + ADCXQ DI, R12 + + // | j1 + + // | w6 @ R12 + MULXQ ·modulus+8(SB), AX, DI + ADOXQ AX, R12 + ADCXQ DI, R13 + + // | j2 + + // | w7 @ R13 + MULXQ ·modulus+16(SB), AX, DI + ADOXQ AX, R13 + ADCXQ DI, R14 + + // | j3 + + // | w8 @ R14 + MULXQ ·modulus+24(SB), AX, DI + ADOXQ AX, R14 + ADCXQ DI, R15 + + // | j4 + + // | w9 @ R15 + MULXQ ·modulus+32(SB), AX, DI + ADOXQ AX, R15 + ADCXQ DI, CX + + // | j5 + + // | w10 @ CX + MULXQ ·modulus+40(SB), AX, DI + ADOXQ AX, CX + + // | w11 @ (SP) + // | move to an idle register + MOVQ (SP), BX + ADCXQ DI, BX + ADOXQ R10, BX + + // | + // | W montgomery reduction ends + // | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - + // | 6 R12 | 7 R13 | 8 R14 | 9 R15 | 10 CX | 11 BX + + + // | + +/* modular reduction */ + + MOVQ R12, AX + SUBQ ·modulus+0(SB), AX + MOVQ R13, DI + SBBQ ·modulus+8(SB), DI + MOVQ R14, SI + SBBQ ·modulus+16(SB), SI + MOVQ R15, R8 + SBBQ ·modulus+24(SB), R8 + MOVQ CX, R9 + SBBQ ·modulus+32(SB), R9 + MOVQ BX, R10 + SBBQ ·modulus+40(SB), R10 + + // | + +/* out */ + + MOVQ c+0(FP), R11 + CMOVQCC AX, R12 + MOVQ R12, (R11) + CMOVQCC DI, R13 + MOVQ R13, 8(R11) + CMOVQCC SI, R14 + MOVQ R14, 16(R11) + CMOVQCC R8, R15 + MOVQ R15, 24(R11) + CMOVQCC R9, CX + MOVQ CX, 32(R11) + CMOVQCC R10, BX + MOVQ BX, 40(R11) + RET + + // | + +/* end */ diff --git a/common/crypto/bls12381/arithmetic_x86_adx.go b/common/crypto/bls12381/arithmetic_x86_adx.go new file mode 100644 index 0000000..fffa568 --- /dev/null +++ b/common/crypto/bls12381/arithmetic_x86_adx.go @@ -0,0 +1,24 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build amd64 && blsadx + +package bls12381 + +// enableADX is true if the ADX/BMI2 instruction set was requested for the BLS +// implementation. The system may still fall back to plain ASM if the necessary +// instructions are unavailable on the CPU. +const enableADX = true diff --git a/common/crypto/bls12381/arithmetic_x86_noadx.go b/common/crypto/bls12381/arithmetic_x86_noadx.go new file mode 100644 index 0000000..ae68090 --- /dev/null +++ b/common/crypto/bls12381/arithmetic_x86_noadx.go @@ -0,0 +1,24 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build amd64 && blsasm + +package bls12381 + +// enableADX is true if the ADX/BMI2 instruction set was requested for the BLS +// implementation. The system may still fall back to plain ASM if the necessary +// instructions are unavailable on the CPU. +const enableADX = false diff --git a/common/crypto/bls12381/bls12_381.go b/common/crypto/bls12381/bls12_381.go new file mode 100644 index 0000000..58d208a --- /dev/null +++ b/common/crypto/bls12381/bls12_381.go @@ -0,0 +1,231 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//nolint:gofmt +package bls12381 + +/* + Field Constants +*/ + +// Base field modulus +// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab + +// Size of six words +// r = 2 ^ 384 + +// modulus = p +var modulus = fe{0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a} + +var ( + // -p^(-1) mod 2^64 + inp uint64 = 0x89f3fffcfffcfffd + // This value is used in assembly code + _ = inp +) + +// r mod p +var r1 = &fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493} + +// r^2 mod p +var r2 = &fe{ + 0xf4df1f341c341746, 0x0a76e6a609d104f1, 0x8de5476c4c95b6d5, 0x67eb88a9939d83c0, 0x9a793e85b519952d, 0x11988fe592cae3aa, +} + +// -1 + 0 * u +var negativeOne2 = &fe2{ + fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, +} + +// 2 ^ (-1) +var twoInv = &fe{0x1804000000015554, 0x855000053ab00001, 0x633cb57c253c276f, 0x6e22d1ec31ebb502, 0xd3916126f2d14ca2, 0x17fbb8571a006596} + +// (p - 3) / 4 +var pMinus3Over4 = bigFromHex("0x680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaaa") + +// (p + 1) / 4 +var pPlus1Over4 = bigFromHex("0x680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaab") + +// (p - 1) / 2 +var pMinus1Over2 = bigFromHex("0xd0088f51cbff34d258dd3db21a5d66bb23ba5c279c2895fb39869507b587b120f55ffff58a9ffffdcff7fffffffd555") + +// -1 +var nonResidue1 = &fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206} + +// (1 + 1 * u) +var nonResidue2 = &fe2{ + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, +} + +/* + Curve Constants +*/ + +// b coefficient for G1 +var b = &fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e} + +// b coefficient for G2 +var b2 = &fe2{ + fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e}, + fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e}, +} + +// Curve order +var q = bigFromHex("0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") + +// Efficient cofactor of G1 +var cofactorEFFG1 = bigFromHex("0xd201000000010001") + +// Efficient cofactor of G2 +var cofactorEFFG2 = bigFromHex("0x0bc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551") + +var g1One = PointG1{ + fe{0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, 0xedce6ecc21dbf440, 0x120177419e0bfb75}, + fe{0xbaac93d50ce72271, 0x8c22631a7918fd8e, 0xdd595f13570725ce, 0x51ac582950405194, 0x0e1c8c3fad0059c0, 0x0bbc3efc5008a26a}, + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, +} + +var g2One = PointG2{ + fe2{ + fe{0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, 0x6f67b7631863366b, 0x058191924350bcd7}, + fe{0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, 0xc2b6ed0ef2158547, 0x11922a097360edf3}, + }, + fe2{ + fe{0x4c730af860494c4a, 0x597cfa1f5e369c5a, 0xe7e6856caa0a635a, 0xbbefb5e96e0d495f, 0x07d3a975f0ef25a2, 0x083fd8e7e80dae5}, + fe{0xadc0fc92df64b05d, 0x18aa270a2b1461dc, 0x86adac6a3be4eba0, 0x79495c4ec93da33a, 0xe7175850a43ccaed, 0xb2bc2a163de1bf2}, + }, + fe2{ + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, +} + +/* + Frobenious Coeffs +*/ + +var frobeniusCoeffs61 = [6]fe2{ + { + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741}, + }, + { + fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + }, + { + fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160}, + }, +} + +var frobeniusCoeffs62 = [6]fe2{ + { + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0xecfb361b798dba3a, 0xc100ddb891865a2c, 0x0ec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x0110f184e51c5f59}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, +} + +var frobeniusCoeffs12 = [12]fe2{ + { + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x07089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x08f2220fb0fb66eb}, + fe{0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf}, + }, + { + fe{0xecfb361b798dba3a, 0xc100ddb891865a2c, 0x0ec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x0110f184e51c5f59}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0x0bd592fc7d825ec8}, + fe{0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0x0e2b7eedbbfd87d2}, + }, + { + fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x0095ba654ed2226b, 0x02e370eccc86f7dd}, + fe{0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd}, + }, + { + fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf}, + fe{0x07089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x08f2220fb0fb66eb}, + }, + { + fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0x0e2b7eedbbfd87d2}, + fe{0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0x0bd592fc7d825ec8}, + }, + { + fe{0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a}, + fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000}, + }, + { + fe{0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd}, + fe{0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x0095ba654ed2226b, 0x02e370eccc86f7dd}, + }, +} + +/* + x +*/ + +var x = bigFromHex("0xd201000000010000") diff --git a/common/crypto/bls12381/bls12_381_test.go b/common/crypto/bls12381/bls12_381_test.go new file mode 100644 index 0000000..6bf5834 --- /dev/null +++ b/common/crypto/bls12381/bls12_381_test.go @@ -0,0 +1,13 @@ +package bls12381 + +import ( + "crypto/rand" + "math/big" +) + +var fuz = 10 + +func randScalar(max *big.Int) *big.Int { + a, _ := rand.Int(rand.Reader, max) + return a +} diff --git a/common/crypto/bls12381/field_element.go b/common/crypto/bls12381/field_element.go new file mode 100644 index 0000000..a983824 --- /dev/null +++ b/common/crypto/bls12381/field_element.go @@ -0,0 +1,342 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//nolint:stylecheck +package bls12381 + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "math/big" +) + +// fe is base field element representation +type fe [6]uint64 + +// fe2 is element representation of 'fp2' which is quadratic extension of base field 'fp' +// Representation follows c[0] + c[1] * u encoding order. +type fe2 [2]fe + +// fe6 is element representation of 'fp6' field which is cubic extension of 'fp2' +// Representation follows c[0] + c[1] * v + c[2] * v^2 encoding order. +type fe6 [3]fe2 + +// fe12 is element representation of 'fp12' field which is quadratic extension of 'fp6' +// Representation follows c[0] + c[1] * w encoding order. +type fe12 [2]fe6 + +func (fe *fe) setBytes(in []byte) *fe { + size := 48 + l := len(in) + if l >= size { + l = size + } + padded := make([]byte, size) + copy(padded[size-l:], in) + var a int + for i := 0; i < 6; i++ { + a = size - i*8 + fe[i] = uint64(padded[a-1]) | uint64(padded[a-2])<<8 | + uint64(padded[a-3])<<16 | uint64(padded[a-4])<<24 | + uint64(padded[a-5])<<32 | uint64(padded[a-6])<<40 | + uint64(padded[a-7])<<48 | uint64(padded[a-8])<<56 + } + return fe +} + +func (fe *fe) setBig(a *big.Int) *fe { + return fe.setBytes(a.Bytes()) +} + +func (fe *fe) setString(s string) (*fe, error) { + if s[:2] == "0x" { + s = s[2:] + } + bytes, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + return fe.setBytes(bytes), nil +} + +func (fe *fe) set(fe2 *fe) *fe { + fe[0] = fe2[0] + fe[1] = fe2[1] + fe[2] = fe2[2] + fe[3] = fe2[3] + fe[4] = fe2[4] + fe[5] = fe2[5] + return fe +} + +func (fe *fe) bytes() []byte { + out := make([]byte, 48) + var a int + for i := 0; i < 6; i++ { + a = 48 - i*8 + out[a-1] = byte(fe[i]) + out[a-2] = byte(fe[i] >> 8) + out[a-3] = byte(fe[i] >> 16) + out[a-4] = byte(fe[i] >> 24) + out[a-5] = byte(fe[i] >> 32) + out[a-6] = byte(fe[i] >> 40) + out[a-7] = byte(fe[i] >> 48) + out[a-8] = byte(fe[i] >> 56) + } + return out +} + +func (fe *fe) big() *big.Int { + return new(big.Int).SetBytes(fe.bytes()) +} + +func (fe *fe) string() (s string) { + for i := 5; i >= 0; i-- { + s = fmt.Sprintf("%s%16.16x", s, fe[i]) + } + return "0x" + s +} + +func (fe *fe) zero() *fe { + fe[0] = 0 + fe[1] = 0 + fe[2] = 0 + fe[3] = 0 + fe[4] = 0 + fe[5] = 0 + return fe +} + +func (fe *fe) one() *fe { + return fe.set(r1) +} + +func (fe *fe) rand(r io.Reader) (*fe, error) { + bi, err := rand.Int(r, modulus.big()) + if err != nil { + return nil, err + } + return fe.setBig(bi), nil +} + +func (fe *fe) isValid() bool { + return fe.cmp(&modulus) < 0 +} + +func (fe *fe) isOdd() bool { + var mask uint64 = 1 + return fe[0]&mask != 0 +} + +func (fe *fe) isEven() bool { + var mask uint64 = 1 + return fe[0]&mask == 0 +} + +func (fe *fe) isZero() bool { + return (fe[5] | fe[4] | fe[3] | fe[2] | fe[1] | fe[0]) == 0 +} + +func (fe *fe) isOne() bool { + return fe.equal(r1) +} + +func (fe *fe) cmp(fe2 *fe) int { + for i := 5; i >= 0; i-- { + if fe[i] > fe2[i] { + return 1 + } else if fe[i] < fe2[i] { + return -1 + } + } + return 0 +} + +func (fe *fe) equal(fe2 *fe) bool { + return fe2[0] == fe[0] && fe2[1] == fe[1] && fe2[2] == fe[2] && fe2[3] == fe[3] && fe2[4] == fe[4] && fe2[5] == fe[5] +} + +func (e *fe) sign() bool { + r := new(fe) + fromMont(r, e) + return r[0]&1 == 0 +} + +//nolint:unparam +func (fe *fe) div2(e uint64) { + fe[0] = fe[0]>>1 | fe[1]<<63 + fe[1] = fe[1]>>1 | fe[2]<<63 + fe[2] = fe[2]>>1 | fe[3]<<63 + fe[3] = fe[3]>>1 | fe[4]<<63 + fe[4] = fe[4]>>1 | fe[5]<<63 + fe[5] = fe[5]>>1 | e<<63 +} + +func (fe *fe) mul2() uint64 { + e := fe[5] >> 63 + fe[5] = fe[5]<<1 | fe[4]>>63 + fe[4] = fe[4]<<1 | fe[3]>>63 + fe[3] = fe[3]<<1 | fe[2]>>63 + fe[2] = fe[2]<<1 | fe[1]>>63 + fe[1] = fe[1]<<1 | fe[0]>>63 + fe[0] = fe[0] << 1 + return e +} + +func (e *fe2) zero() *fe2 { + e[0].zero() + e[1].zero() + return e +} + +func (e *fe2) one() *fe2 { + e[0].one() + e[1].zero() + return e +} + +func (e *fe2) set(e2 *fe2) *fe2 { + e[0].set(&e2[0]) + e[1].set(&e2[1]) + return e +} + +func (e *fe2) rand(r io.Reader) (*fe2, error) { + a0, err := new(fe).rand(r) + if err != nil { + return nil, err + } + a1, err := new(fe).rand(r) + if err != nil { + return nil, err + } + return &fe2{*a0, *a1}, nil +} + +func (e *fe2) isOne() bool { + return e[0].isOne() && e[1].isZero() +} + +func (e *fe2) isZero() bool { + return e[0].isZero() && e[1].isZero() +} + +func (e *fe2) equal(e2 *fe2) bool { + return e[0].equal(&e2[0]) && e[1].equal(&e2[1]) +} + +func (e *fe2) sign() bool { + r := new(fe) + if !e[0].isZero() { + fromMont(r, &e[0]) + return r[0]&1 == 0 + } + fromMont(r, &e[1]) + return r[0]&1 == 0 +} + +func (e *fe6) zero() *fe6 { + e[0].zero() + e[1].zero() + e[2].zero() + return e +} + +func (e *fe6) one() *fe6 { + e[0].one() + e[1].zero() + e[2].zero() + return e +} + +func (e *fe6) set(e2 *fe6) *fe6 { + e[0].set(&e2[0]) + e[1].set(&e2[1]) + e[2].set(&e2[2]) + return e +} + +func (e *fe6) rand(r io.Reader) (*fe6, error) { + a0, err := new(fe2).rand(r) + if err != nil { + return nil, err + } + a1, err := new(fe2).rand(r) + if err != nil { + return nil, err + } + a2, err := new(fe2).rand(r) + if err != nil { + return nil, err + } + return &fe6{*a0, *a1, *a2}, nil +} + +func (e *fe6) isOne() bool { + return e[0].isOne() && e[1].isZero() && e[2].isZero() +} + +func (e *fe6) isZero() bool { + return e[0].isZero() && e[1].isZero() && e[2].isZero() +} + +func (e *fe6) equal(e2 *fe6) bool { + return e[0].equal(&e2[0]) && e[1].equal(&e2[1]) && e[2].equal(&e2[2]) +} + +func (e *fe12) zero() *fe12 { + e[0].zero() + e[1].zero() + return e +} + +func (e *fe12) one() *fe12 { + e[0].one() + e[1].zero() + return e +} + +func (e *fe12) set(e2 *fe12) *fe12 { + e[0].set(&e2[0]) + e[1].set(&e2[1]) + return e +} + +func (e *fe12) rand(r io.Reader) (*fe12, error) { + a0, err := new(fe6).rand(r) + if err != nil { + return nil, err + } + a1, err := new(fe6).rand(r) + if err != nil { + return nil, err + } + return &fe12{*a0, *a1}, nil +} + +func (e *fe12) isOne() bool { + return e[0].isOne() && e[1].isZero() +} + +func (e *fe12) isZero() bool { + return e[0].isZero() && e[1].isZero() +} + +func (e *fe12) equal(e2 *fe12) bool { + return e[0].equal(&e2[0]) && e[1].equal(&e2[1]) +} diff --git a/common/crypto/bls12381/field_element_test.go b/common/crypto/bls12381/field_element_test.go new file mode 100644 index 0000000..0f6abd2 --- /dev/null +++ b/common/crypto/bls12381/field_element_test.go @@ -0,0 +1,251 @@ +package bls12381 + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +func TestFieldElementValidation(t *testing.T) { + zero := new(fe).zero() + if !zero.isValid() { + t.Fatal("zero must be valid") + } + one := new(fe).one() + if !one.isValid() { + t.Fatal("one must be valid") + } + if modulus.isValid() { + t.Fatal("modulus must be invalid") + } + n := modulus.big() + n.Add(n, big.NewInt(1)) + if new(fe).setBig(n).isValid() { + t.Fatal("number greater than modulus must be invalid") + } +} + +func TestFieldElementEquality(t *testing.T) { + // fe + zero := new(fe).zero() + if !zero.equal(zero) { + t.Fatal("0 == 0") + } + one := new(fe).one() + if !one.equal(one) { + t.Fatal("1 == 1") + } + a, _ := new(fe).rand(rand.Reader) + if !a.equal(a) { + t.Fatal("a == a") + } + b := new(fe) + add(b, a, one) + if a.equal(b) { + t.Fatal("a != a + 1") + } + // fe2 + zero2 := new(fe2).zero() + if !zero2.equal(zero2) { + t.Fatal("0 == 0") + } + one2 := new(fe2).one() + if !one2.equal(one2) { + t.Fatal("1 == 1") + } + a2, _ := new(fe2).rand(rand.Reader) + if !a2.equal(a2) { + t.Fatal("a == a") + } + b2 := new(fe2) + fp2 := newFp2() + fp2.add(b2, a2, one2) + if a2.equal(b2) { + t.Fatal("a != a + 1") + } + // fe6 + zero6 := new(fe6).zero() + if !zero6.equal(zero6) { + t.Fatal("0 == 0") + } + one6 := new(fe6).one() + if !one6.equal(one6) { + t.Fatal("1 == 1") + } + a6, _ := new(fe6).rand(rand.Reader) + if !a6.equal(a6) { + t.Fatal("a == a") + } + b6 := new(fe6) + fp6 := newFp6(fp2) + fp6.add(b6, a6, one6) + if a6.equal(b6) { + t.Fatal("a != a + 1") + } + // fe12 + zero12 := new(fe12).zero() + if !zero12.equal(zero12) { + t.Fatal("0 == 0") + } + one12 := new(fe12).one() + if !one12.equal(one12) { + t.Fatal("1 == 1") + } + a12, _ := new(fe12).rand(rand.Reader) + if !a12.equal(a12) { + t.Fatal("a == a") + } + b12 := new(fe12) + fp12 := newFp12(fp6) + fp12.add(b12, a12, one12) + if a12.equal(b12) { + t.Fatal("a != a + 1") + } + +} + +func TestFieldElementHelpers(t *testing.T) { + // fe + zero := new(fe).zero() + if !zero.isZero() { + t.Fatal("'zero' is not zero") + } + one := new(fe).one() + if !one.isOne() { + t.Fatal("'one' is not one") + } + odd := new(fe).setBig(big.NewInt(1)) + if !odd.isOdd() { + t.Fatal("1 must be odd") + } + if odd.isEven() { + t.Fatal("1 must not be even") + } + even := new(fe).setBig(big.NewInt(2)) + if !even.isEven() { + t.Fatal("2 must be even") + } + if even.isOdd() { + t.Fatal("2 must not be odd") + } + // fe2 + zero2 := new(fe2).zero() + if !zero2.isZero() { + t.Fatal("'zero' is not zero, 2") + } + one2 := new(fe2).one() + if !one2.isOne() { + t.Fatal("'one' is not one, 2") + } + // fe6 + zero6 := new(fe6).zero() + if !zero6.isZero() { + t.Fatal("'zero' is not zero, 6") + } + one6 := new(fe6).one() + if !one6.isOne() { + t.Fatal("'one' is not one, 6") + } + // fe12 + zero12 := new(fe12).zero() + if !zero12.isZero() { + t.Fatal("'zero' is not zero, 12") + } + one12 := new(fe12).one() + if !one12.isOne() { + t.Fatal("'one' is not one, 12") + } +} + +func TestFieldElementSerialization(t *testing.T) { + t.Run("zero", func(t *testing.T) { + in := make([]byte, 48) + fe := new(fe).setBytes(in) + if !fe.isZero() { + t.Fatal("bad serialization") + } + if !bytes.Equal(in, fe.bytes()) { + t.Fatal("bad serialization") + } + }) + t.Run("bytes", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b := new(fe).setBytes(a.bytes()) + if !a.equal(b) { + t.Fatal("bad serialization") + } + } + }) + t.Run("big", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b := new(fe).setBig(a.big()) + if !a.equal(b) { + t.Fatal("bad encoding or decoding") + } + } + }) + t.Run("string", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, err := new(fe).setString(a.string()) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad encoding or decoding") + } + } + }) +} + +func TestFieldElementByteInputs(t *testing.T) { + zero := new(fe).zero() + in := make([]byte, 0) + a := new(fe).setBytes(in) + if !a.equal(zero) { + t.Fatal("bad serialization") + } + in = make([]byte, 48) + a = new(fe).setBytes(in) + if !a.equal(zero) { + t.Fatal("bad serialization") + } + in = make([]byte, 64) + a = new(fe).setBytes(in) + if !a.equal(zero) { + t.Fatal("bad serialization") + } + in = make([]byte, 49) + in[47] = 1 + normalOne := &fe{1, 0, 0, 0, 0, 0} + a = new(fe).setBytes(in) + if !a.equal(normalOne) { + t.Fatal("bad serialization") + } +} + +func TestFieldElementCopy(t *testing.T) { + a, _ := new(fe).rand(rand.Reader) + b := new(fe).set(a) + if !a.equal(b) { + t.Fatal("bad copy, 1") + } + a2, _ := new(fe2).rand(rand.Reader) + b2 := new(fe2).set(a2) + if !a2.equal(b2) { + t.Fatal("bad copy, 2") + } + a6, _ := new(fe6).rand(rand.Reader) + b6 := new(fe6).set(a6) + if !a6.equal(b6) { + t.Fatal("bad copy, 6") + } + a12, _ := new(fe12).rand(rand.Reader) + b12 := new(fe12).set(a12) + if !a12.equal(b12) { + t.Fatal("bad copy, 12") + } +} diff --git a/common/crypto/bls12381/fp.go b/common/crypto/bls12381/fp.go new file mode 100644 index 0000000..d850b5c --- /dev/null +++ b/common/crypto/bls12381/fp.go @@ -0,0 +1,167 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +import ( + "errors" + "math/big" +) + +func fromBytes(in []byte) (*fe, error) { + fe := &fe{} + if len(in) != 48 { + return nil, errors.New("input string should be equal 48 bytes") + } + fe.setBytes(in) + if !fe.isValid() { + return nil, errors.New("must be less than modulus") + } + toMont(fe, fe) + return fe, nil +} + +func fromBig(in *big.Int) (*fe, error) { + fe := new(fe).setBig(in) + if !fe.isValid() { + return nil, errors.New("invalid input string") + } + toMont(fe, fe) + return fe, nil +} + +func fromString(in string) (*fe, error) { + fe, err := new(fe).setString(in) + if err != nil { + return nil, err + } + if !fe.isValid() { + return nil, errors.New("invalid input string") + } + toMont(fe, fe) + return fe, nil +} + +func toBytes(e *fe) []byte { + e2 := new(fe) + fromMont(e2, e) + return e2.bytes() +} + +func toBig(e *fe) *big.Int { + e2 := new(fe) + fromMont(e2, e) + return e2.big() +} + +func toString(e *fe) (s string) { + e2 := new(fe) + fromMont(e2, e) + return e2.string() +} + +func toMont(c, a *fe) { + mul(c, a, r2) +} + +func fromMont(c, a *fe) { + mul(c, a, &fe{1}) +} + +func exp(c, a *fe, e *big.Int) { + z := new(fe).set(r1) + for i := e.BitLen(); i >= 0; i-- { + mul(z, z, z) + if e.Bit(i) == 1 { + mul(z, z, a) + } + } + c.set(z) +} + +func inverse(inv, e *fe) { + if e.isZero() { + inv.zero() + return + } + u := new(fe).set(&modulus) + v := new(fe).set(e) + s := &fe{1} + r := &fe{0} + var k int + var z uint64 + var found = false + // Phase 1 + for i := 0; i < 768; i++ { + if v.isZero() { + found = true + break + } + if u.isEven() { + u.div2(0) + s.mul2() + } else if v.isEven() { + v.div2(0) + z += r.mul2() + } else if u.cmp(v) == 1 { + lsubAssign(u, v) + u.div2(0) + laddAssign(r, s) + s.mul2() + } else { + lsubAssign(v, u) + v.div2(0) + laddAssign(s, r) + z += r.mul2() + } + k++ + } + + if !found { + inv.zero() + return + } + + if k < 381 || k > 381+384 { + inv.zero() + return + } + + if r.cmp(&modulus) != -1 || z > 0 { + lsubAssign(r, &modulus) + } + u.set(&modulus) + lsubAssign(u, r) + + // Phase 2 + for i := k; i < 384*2; i++ { + double(u, u) + } + inv.set(u) +} + +func sqrt(c, a *fe) bool { + u, v := new(fe).set(a), new(fe) + exp(c, a, pPlus1Over4) + square(v, c) + return u.equal(v) +} + +func isQuadraticNonResidue(elem *fe) bool { + result := new(fe) + exp(result, elem, pMinus1Over2) + return !result.isOne() +} diff --git a/common/crypto/bls12381/fp12.go b/common/crypto/bls12381/fp12.go new file mode 100644 index 0000000..bd7d7ec --- /dev/null +++ b/common/crypto/bls12381/fp12.go @@ -0,0 +1,279 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +import ( + "errors" + "math/big" +) + +type fp12 struct { + fp12temp + fp6 *fp6 +} + +type fp12temp struct { + t2 [9]*fe2 + t6 [5]*fe6 + t12 *fe12 +} + +func newFp12Temp() fp12temp { + t2 := [9]*fe2{} + t6 := [5]*fe6{} + for i := 0; i < len(t2); i++ { + t2[i] = &fe2{} + } + for i := 0; i < len(t6); i++ { + t6[i] = &fe6{} + } + return fp12temp{t2, t6, &fe12{}} +} + +func newFp12(fp6 *fp6) *fp12 { + t := newFp12Temp() + if fp6 == nil { + return &fp12{t, newFp6(nil)} + } + return &fp12{t, fp6} +} + +func (e *fp12) fp2() *fp2 { + return e.fp6.fp2 +} + +func (e *fp12) fromBytes(in []byte) (*fe12, error) { + if len(in) != 576 { + return nil, errors.New("input string should be larger than 96 bytes") + } + fp6 := e.fp6 + c1, err := fp6.fromBytes(in[:288]) + if err != nil { + return nil, err + } + c0, err := fp6.fromBytes(in[288:]) + if err != nil { + return nil, err + } + return &fe12{*c0, *c1}, nil +} + +func (e *fp12) toBytes(a *fe12) []byte { + fp6 := e.fp6 + out := make([]byte, 576) + copy(out[:288], fp6.toBytes(&a[1])) + copy(out[288:], fp6.toBytes(&a[0])) + return out +} + +func (e *fp12) new() *fe12 { + return new(fe12) +} + +func (e *fp12) zero() *fe12 { + return new(fe12) +} + +func (e *fp12) one() *fe12 { + return new(fe12).one() +} + +func (e *fp12) add(c, a, b *fe12) { + fp6 := e.fp6 + fp6.add(&c[0], &a[0], &b[0]) + fp6.add(&c[1], &a[1], &b[1]) + +} + +func (e *fp12) double(c, a *fe12) { + fp6 := e.fp6 + fp6.double(&c[0], &a[0]) + fp6.double(&c[1], &a[1]) +} + +func (e *fp12) sub(c, a, b *fe12) { + fp6 := e.fp6 + fp6.sub(&c[0], &a[0], &b[0]) + fp6.sub(&c[1], &a[1], &b[1]) + +} + +func (e *fp12) neg(c, a *fe12) { + fp6 := e.fp6 + fp6.neg(&c[0], &a[0]) + fp6.neg(&c[1], &a[1]) +} + +func (e *fp12) conjugate(c, a *fe12) { + fp6 := e.fp6 + c[0].set(&a[0]) + fp6.neg(&c[1], &a[1]) +} + +func (e *fp12) square(c, a *fe12) { + fp6, t := e.fp6, e.t6 + fp6.add(t[0], &a[0], &a[1]) + fp6.mul(t[2], &a[0], &a[1]) + fp6.mulByNonResidue(t[1], &a[1]) + fp6.addAssign(t[1], &a[0]) + fp6.mulByNonResidue(t[3], t[2]) + fp6.mulAssign(t[0], t[1]) + fp6.subAssign(t[0], t[2]) + fp6.sub(&c[0], t[0], t[3]) + fp6.double(&c[1], t[2]) +} + +func (e *fp12) cyclotomicSquare(c, a *fe12) { + t, fp2 := e.t2, e.fp2() + e.fp4Square(t[3], t[4], &a[0][0], &a[1][1]) + fp2.sub(t[2], t[3], &a[0][0]) + fp2.doubleAssign(t[2]) + fp2.add(&c[0][0], t[2], t[3]) + fp2.add(t[2], t[4], &a[1][1]) + fp2.doubleAssign(t[2]) + fp2.add(&c[1][1], t[2], t[4]) + e.fp4Square(t[3], t[4], &a[1][0], &a[0][2]) + e.fp4Square(t[5], t[6], &a[0][1], &a[1][2]) + fp2.sub(t[2], t[3], &a[0][1]) + fp2.doubleAssign(t[2]) + fp2.add(&c[0][1], t[2], t[3]) + fp2.add(t[2], t[4], &a[1][2]) + fp2.doubleAssign(t[2]) + fp2.add(&c[1][2], t[2], t[4]) + fp2.mulByNonResidue(t[3], t[6]) + fp2.add(t[2], t[3], &a[1][0]) + fp2.doubleAssign(t[2]) + fp2.add(&c[1][0], t[2], t[3]) + fp2.sub(t[2], t[5], &a[0][2]) + fp2.doubleAssign(t[2]) + fp2.add(&c[0][2], t[2], t[5]) +} + +func (e *fp12) mul(c, a, b *fe12) { + t, fp6 := e.t6, e.fp6 + fp6.mul(t[1], &a[0], &b[0]) + fp6.mul(t[2], &a[1], &b[1]) + fp6.add(t[0], t[1], t[2]) + fp6.mulByNonResidue(t[2], t[2]) + fp6.add(t[3], t[1], t[2]) + fp6.add(t[1], &a[0], &a[1]) + fp6.add(t[2], &b[0], &b[1]) + fp6.mulAssign(t[1], t[2]) + c[0].set(t[3]) + fp6.sub(&c[1], t[1], t[0]) +} + +func (e *fp12) mulAssign(a, b *fe12) { + t, fp6 := e.t6, e.fp6 + fp6.mul(t[1], &a[0], &b[0]) + fp6.mul(t[2], &a[1], &b[1]) + fp6.add(t[0], t[1], t[2]) + fp6.mulByNonResidue(t[2], t[2]) + fp6.add(t[3], t[1], t[2]) + fp6.add(t[1], &a[0], &a[1]) + fp6.add(t[2], &b[0], &b[1]) + fp6.mulAssign(t[1], t[2]) + a[0].set(t[3]) + fp6.sub(&a[1], t[1], t[0]) +} + +func (e *fp12) fp4Square(c0, c1, a0, a1 *fe2) { + t, fp2 := e.t2, e.fp2() + fp2.square(t[0], a0) + fp2.square(t[1], a1) + fp2.mulByNonResidue(t[2], t[1]) + fp2.add(c0, t[2], t[0]) + fp2.add(t[2], a0, a1) + fp2.squareAssign(t[2]) + fp2.subAssign(t[2], t[0]) + fp2.sub(c1, t[2], t[1]) +} + +func (e *fp12) inverse(c, a *fe12) { + fp6, t := e.fp6, e.t6 + fp6.square(t[0], &a[0]) + fp6.square(t[1], &a[1]) + fp6.mulByNonResidue(t[1], t[1]) + fp6.sub(t[1], t[0], t[1]) + fp6.inverse(t[0], t[1]) + fp6.mul(&c[0], &a[0], t[0]) + fp6.mulAssign(t[0], &a[1]) + fp6.neg(&c[1], t[0]) +} + +func (e *fp12) mulBy014Assign(a *fe12, c0, c1, c4 *fe2) { + fp2, fp6, t, t2 := e.fp2(), e.fp6, e.t6, e.t2[0] + fp6.mulBy01(t[0], &a[0], c0, c1) + fp6.mulBy1(t[1], &a[1], c4) + fp2.add(t2, c1, c4) + fp6.add(t[2], &a[1], &a[0]) + fp6.mulBy01Assign(t[2], c0, t2) + fp6.subAssign(t[2], t[0]) + fp6.sub(&a[1], t[2], t[1]) + fp6.mulByNonResidue(t[1], t[1]) + fp6.add(&a[0], t[1], t[0]) +} + +func (e *fp12) exp(c, a *fe12, s *big.Int) { + z := e.one() + for i := s.BitLen() - 1; i >= 0; i-- { + e.square(z, z) + if s.Bit(i) == 1 { + e.mul(z, z, a) + } + } + c.set(z) +} + +func (e *fp12) cyclotomicExp(c, a *fe12, s *big.Int) { + z := e.one() + for i := s.BitLen() - 1; i >= 0; i-- { + e.cyclotomicSquare(z, z) + if s.Bit(i) == 1 { + e.mul(z, z, a) + } + } + c.set(z) +} + +func (e *fp12) frobeniusMap(c, a *fe12, power uint) { + fp6 := e.fp6 + fp6.frobeniusMap(&c[0], &a[0], power) + fp6.frobeniusMap(&c[1], &a[1], power) + switch power { + case 0: + return + case 6: + fp6.neg(&c[1], &c[1]) + default: + fp6.mulByBaseField(&c[1], &c[1], &frobeniusCoeffs12[power]) + } +} + +func (e *fp12) frobeniusMapAssign(a *fe12, power uint) { + fp6 := e.fp6 + fp6.frobeniusMapAssign(&a[0], power) + fp6.frobeniusMapAssign(&a[1], power) + switch power { + case 0: + return + case 6: + fp6.neg(&a[1], &a[1]) + default: + fp6.mulByBaseField(&a[1], &a[1], &frobeniusCoeffs12[power]) + } +} diff --git a/common/crypto/bls12381/fp2.go b/common/crypto/bls12381/fp2.go new file mode 100644 index 0000000..785330f --- /dev/null +++ b/common/crypto/bls12381/fp2.go @@ -0,0 +1,252 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +import ( + "errors" + "math/big" +) + +type fp2Temp struct { + t [4]*fe +} + +type fp2 struct { + fp2Temp +} + +func newFp2Temp() fp2Temp { + t := [4]*fe{} + for i := 0; i < len(t); i++ { + t[i] = &fe{} + } + return fp2Temp{t} +} + +func newFp2() *fp2 { + t := newFp2Temp() + return &fp2{t} +} + +func (e *fp2) fromBytes(in []byte) (*fe2, error) { + if len(in) != 96 { + return nil, errors.New("length of input string should be 96 bytes") + } + c1, err := fromBytes(in[:48]) + if err != nil { + return nil, err + } + c0, err := fromBytes(in[48:]) + if err != nil { + return nil, err + } + return &fe2{*c0, *c1}, nil +} + +func (e *fp2) toBytes(a *fe2) []byte { + out := make([]byte, 96) + copy(out[:48], toBytes(&a[1])) + copy(out[48:], toBytes(&a[0])) + return out +} + +func (e *fp2) new() *fe2 { + return new(fe2).zero() +} + +func (e *fp2) zero() *fe2 { + return new(fe2).zero() +} + +func (e *fp2) one() *fe2 { + return new(fe2).one() +} + +func (e *fp2) add(c, a, b *fe2) { + add(&c[0], &a[0], &b[0]) + add(&c[1], &a[1], &b[1]) +} + +func (e *fp2) addAssign(a, b *fe2) { + addAssign(&a[0], &b[0]) + addAssign(&a[1], &b[1]) +} + +func (e *fp2) ladd(c, a, b *fe2) { + ladd(&c[0], &a[0], &b[0]) + ladd(&c[1], &a[1], &b[1]) +} + +func (e *fp2) double(c, a *fe2) { + double(&c[0], &a[0]) + double(&c[1], &a[1]) +} + +func (e *fp2) doubleAssign(a *fe2) { + doubleAssign(&a[0]) + doubleAssign(&a[1]) +} + +func (e *fp2) ldouble(c, a *fe2) { + ldouble(&c[0], &a[0]) + ldouble(&c[1], &a[1]) +} + +func (e *fp2) sub(c, a, b *fe2) { + sub(&c[0], &a[0], &b[0]) + sub(&c[1], &a[1], &b[1]) +} + +func (e *fp2) subAssign(c, a *fe2) { + subAssign(&c[0], &a[0]) + subAssign(&c[1], &a[1]) +} + +func (e *fp2) neg(c, a *fe2) { + neg(&c[0], &a[0]) + neg(&c[1], &a[1]) +} + +func (e *fp2) mul(c, a, b *fe2) { + t := e.t + mul(t[1], &a[0], &b[0]) + mul(t[2], &a[1], &b[1]) + add(t[0], &a[0], &a[1]) + add(t[3], &b[0], &b[1]) + sub(&c[0], t[1], t[2]) + addAssign(t[1], t[2]) + mul(t[0], t[0], t[3]) + sub(&c[1], t[0], t[1]) +} + +func (e *fp2) mulAssign(a, b *fe2) { + t := e.t + mul(t[1], &a[0], &b[0]) + mul(t[2], &a[1], &b[1]) + add(t[0], &a[0], &a[1]) + add(t[3], &b[0], &b[1]) + sub(&a[0], t[1], t[2]) + addAssign(t[1], t[2]) + mul(t[0], t[0], t[3]) + sub(&a[1], t[0], t[1]) +} + +func (e *fp2) square(c, a *fe2) { + t := e.t + ladd(t[0], &a[0], &a[1]) + sub(t[1], &a[0], &a[1]) + ldouble(t[2], &a[0]) + mul(&c[0], t[0], t[1]) + mul(&c[1], t[2], &a[1]) +} + +func (e *fp2) squareAssign(a *fe2) { + t := e.t + ladd(t[0], &a[0], &a[1]) + sub(t[1], &a[0], &a[1]) + ldouble(t[2], &a[0]) + mul(&a[0], t[0], t[1]) + mul(&a[1], t[2], &a[1]) +} + +func (e *fp2) mulByNonResidue(c, a *fe2) { + t := e.t + sub(t[0], &a[0], &a[1]) + add(&c[1], &a[0], &a[1]) + c[0].set(t[0]) +} + +func (e *fp2) mulByB(c, a *fe2) { + t := e.t + double(t[0], &a[0]) + double(t[1], &a[1]) + doubleAssign(t[0]) + doubleAssign(t[1]) + sub(&c[0], t[0], t[1]) + add(&c[1], t[0], t[1]) +} + +func (e *fp2) inverse(c, a *fe2) { + t := e.t + square(t[0], &a[0]) + square(t[1], &a[1]) + addAssign(t[0], t[1]) + inverse(t[0], t[0]) + mul(&c[0], &a[0], t[0]) + mul(t[0], t[0], &a[1]) + neg(&c[1], t[0]) +} + +func (e *fp2) mulByFq(c, a *fe2, b *fe) { + mul(&c[0], &a[0], b) + mul(&c[1], &a[1], b) +} + +func (e *fp2) exp(c, a *fe2, s *big.Int) { + z := e.one() + for i := s.BitLen() - 1; i >= 0; i-- { + e.square(z, z) + if s.Bit(i) == 1 { + e.mul(z, z, a) + } + } + c.set(z) +} + +func (e *fp2) frobeniusMap(c, a *fe2, power uint) { + c[0].set(&a[0]) + if power%2 == 1 { + neg(&c[1], &a[1]) + return + } + c[1].set(&a[1]) +} + +func (e *fp2) frobeniusMapAssign(a *fe2, power uint) { + if power%2 == 1 { + neg(&a[1], &a[1]) + return + } +} + +func (e *fp2) sqrt(c, a *fe2) bool { + u, x0, a1, alpha := &fe2{}, &fe2{}, &fe2{}, &fe2{} + u.set(a) + e.exp(a1, a, pMinus3Over4) + e.square(alpha, a1) + e.mul(alpha, alpha, a) + e.mul(x0, a1, a) + if alpha.equal(negativeOne2) { + neg(&c[0], &x0[1]) + c[1].set(&x0[0]) + return true + } + e.add(alpha, alpha, e.one()) + e.exp(alpha, alpha, pMinus1Over2) + e.mul(c, alpha, x0) + e.square(alpha, c) + return alpha.equal(u) +} + +func (e *fp2) isQuadraticNonResidue(a *fe2) bool { + // https://github.com/leovt/constructible/wiki/Taking-Square-Roots-in-quadratic-extension-Fields + c0, c1 := new(fe), new(fe) + square(c0, &a[0]) + square(c1, &a[1]) + add(c1, c1, c0) + return isQuadraticNonResidue(c1) +} diff --git a/common/crypto/bls12381/fp6.go b/common/crypto/bls12381/fp6.go new file mode 100644 index 0000000..e913505 --- /dev/null +++ b/common/crypto/bls12381/fp6.go @@ -0,0 +1,351 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +import ( + "errors" + "math/big" +) + +type fp6Temp struct { + t [6]*fe2 +} + +type fp6 struct { + fp2 *fp2 + fp6Temp +} + +func newFp6Temp() fp6Temp { + t := [6]*fe2{} + for i := 0; i < len(t); i++ { + t[i] = &fe2{} + } + return fp6Temp{t} +} + +func newFp6(f *fp2) *fp6 { + t := newFp6Temp() + if f == nil { + return &fp6{newFp2(), t} + } + return &fp6{f, t} +} + +func (e *fp6) fromBytes(b []byte) (*fe6, error) { + if len(b) < 288 { + return nil, errors.New("input string should be larger than 288 bytes") + } + fp2 := e.fp2 + u2, err := fp2.fromBytes(b[:96]) + if err != nil { + return nil, err + } + u1, err := fp2.fromBytes(b[96:192]) + if err != nil { + return nil, err + } + u0, err := fp2.fromBytes(b[192:]) + if err != nil { + return nil, err + } + return &fe6{*u0, *u1, *u2}, nil +} + +func (e *fp6) toBytes(a *fe6) []byte { + fp2 := e.fp2 + out := make([]byte, 288) + copy(out[:96], fp2.toBytes(&a[2])) + copy(out[96:192], fp2.toBytes(&a[1])) + copy(out[192:], fp2.toBytes(&a[0])) + return out +} + +func (e *fp6) new() *fe6 { + return new(fe6) +} + +func (e *fp6) zero() *fe6 { + return new(fe6) +} + +func (e *fp6) one() *fe6 { + return new(fe6).one() +} + +func (e *fp6) add(c, a, b *fe6) { + fp2 := e.fp2 + fp2.add(&c[0], &a[0], &b[0]) + fp2.add(&c[1], &a[1], &b[1]) + fp2.add(&c[2], &a[2], &b[2]) +} + +func (e *fp6) addAssign(a, b *fe6) { + fp2 := e.fp2 + fp2.addAssign(&a[0], &b[0]) + fp2.addAssign(&a[1], &b[1]) + fp2.addAssign(&a[2], &b[2]) +} + +func (e *fp6) double(c, a *fe6) { + fp2 := e.fp2 + fp2.double(&c[0], &a[0]) + fp2.double(&c[1], &a[1]) + fp2.double(&c[2], &a[2]) +} + +func (e *fp6) doubleAssign(a *fe6) { + fp2 := e.fp2 + fp2.doubleAssign(&a[0]) + fp2.doubleAssign(&a[1]) + fp2.doubleAssign(&a[2]) +} + +func (e *fp6) sub(c, a, b *fe6) { + fp2 := e.fp2 + fp2.sub(&c[0], &a[0], &b[0]) + fp2.sub(&c[1], &a[1], &b[1]) + fp2.sub(&c[2], &a[2], &b[2]) +} + +func (e *fp6) subAssign(a, b *fe6) { + fp2 := e.fp2 + fp2.subAssign(&a[0], &b[0]) + fp2.subAssign(&a[1], &b[1]) + fp2.subAssign(&a[2], &b[2]) +} + +func (e *fp6) neg(c, a *fe6) { + fp2 := e.fp2 + fp2.neg(&c[0], &a[0]) + fp2.neg(&c[1], &a[1]) + fp2.neg(&c[2], &a[2]) +} + +func (e *fp6) mul(c, a, b *fe6) { + fp2, t := e.fp2, e.t + fp2.mul(t[0], &a[0], &b[0]) + fp2.mul(t[1], &a[1], &b[1]) + fp2.mul(t[2], &a[2], &b[2]) + fp2.add(t[3], &a[1], &a[2]) + fp2.add(t[4], &b[1], &b[2]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[1], t[2]) + fp2.subAssign(t[3], t[4]) + fp2.mulByNonResidue(t[3], t[3]) + fp2.add(t[5], t[0], t[3]) + fp2.add(t[3], &a[0], &a[1]) + fp2.add(t[4], &b[0], &b[1]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[0], t[1]) + fp2.subAssign(t[3], t[4]) + fp2.mulByNonResidue(t[4], t[2]) + fp2.add(&c[1], t[3], t[4]) + fp2.add(t[3], &a[0], &a[2]) + fp2.add(t[4], &b[0], &b[2]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[0], t[2]) + fp2.subAssign(t[3], t[4]) + fp2.add(&c[2], t[1], t[3]) + c[0].set(t[5]) +} + +func (e *fp6) mulAssign(a, b *fe6) { + fp2, t := e.fp2, e.t + fp2.mul(t[0], &a[0], &b[0]) + fp2.mul(t[1], &a[1], &b[1]) + fp2.mul(t[2], &a[2], &b[2]) + fp2.add(t[3], &a[1], &a[2]) + fp2.add(t[4], &b[1], &b[2]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[1], t[2]) + fp2.subAssign(t[3], t[4]) + fp2.mulByNonResidue(t[3], t[3]) + fp2.add(t[5], t[0], t[3]) + fp2.add(t[3], &a[0], &a[1]) + fp2.add(t[4], &b[0], &b[1]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[0], t[1]) + fp2.subAssign(t[3], t[4]) + fp2.mulByNonResidue(t[4], t[2]) + fp2.add(&a[1], t[3], t[4]) + fp2.add(t[3], &a[0], &a[2]) + fp2.add(t[4], &b[0], &b[2]) + fp2.mulAssign(t[3], t[4]) + fp2.add(t[4], t[0], t[2]) + fp2.subAssign(t[3], t[4]) + fp2.add(&a[2], t[1], t[3]) + a[0].set(t[5]) +} + +func (e *fp6) square(c, a *fe6) { + fp2, t := e.fp2, e.t + fp2.square(t[0], &a[0]) + fp2.mul(t[1], &a[0], &a[1]) + fp2.doubleAssign(t[1]) + fp2.sub(t[2], &a[0], &a[1]) + fp2.addAssign(t[2], &a[2]) + fp2.squareAssign(t[2]) + fp2.mul(t[3], &a[1], &a[2]) + fp2.doubleAssign(t[3]) + fp2.square(t[4], &a[2]) + fp2.mulByNonResidue(t[5], t[3]) + fp2.add(&c[0], t[0], t[5]) + fp2.mulByNonResidue(t[5], t[4]) + fp2.add(&c[1], t[1], t[5]) + fp2.addAssign(t[1], t[2]) + fp2.addAssign(t[1], t[3]) + fp2.addAssign(t[0], t[4]) + fp2.sub(&c[2], t[1], t[0]) +} + +func (e *fp6) mulBy01Assign(a *fe6, b0, b1 *fe2) { + fp2, t := e.fp2, e.t + fp2.mul(t[0], &a[0], b0) + fp2.mul(t[1], &a[1], b1) + fp2.add(t[5], &a[1], &a[2]) + fp2.mul(t[2], b1, t[5]) + fp2.subAssign(t[2], t[1]) + fp2.mulByNonResidue(t[2], t[2]) + fp2.add(t[5], &a[0], &a[2]) + fp2.mul(t[3], b0, t[5]) + fp2.subAssign(t[3], t[0]) + fp2.add(&a[2], t[3], t[1]) + fp2.add(t[4], b0, b1) + fp2.add(t[5], &a[0], &a[1]) + fp2.mulAssign(t[4], t[5]) + fp2.subAssign(t[4], t[0]) + fp2.sub(&a[1], t[4], t[1]) + fp2.add(&a[0], t[2], t[0]) +} + +func (e *fp6) mulBy01(c, a *fe6, b0, b1 *fe2) { + fp2, t := e.fp2, e.t + fp2.mul(t[0], &a[0], b0) + fp2.mul(t[1], &a[1], b1) + fp2.add(t[2], &a[1], &a[2]) + fp2.mulAssign(t[2], b1) + fp2.subAssign(t[2], t[1]) + fp2.mulByNonResidue(t[2], t[2]) + fp2.add(t[3], &a[0], &a[2]) + fp2.mulAssign(t[3], b0) + fp2.subAssign(t[3], t[0]) + fp2.add(&c[2], t[3], t[1]) + fp2.add(t[4], b0, b1) + fp2.add(t[3], &a[0], &a[1]) + fp2.mulAssign(t[4], t[3]) + fp2.subAssign(t[4], t[0]) + fp2.sub(&c[1], t[4], t[1]) + fp2.add(&c[0], t[2], t[0]) +} + +func (e *fp6) mulBy1(c, a *fe6, b1 *fe2) { + fp2, t := e.fp2, e.t + fp2.mul(t[0], &a[2], b1) + fp2.mul(&c[2], &a[1], b1) + fp2.mul(&c[1], &a[0], b1) + fp2.mulByNonResidue(&c[0], t[0]) +} + +func (e *fp6) mulByNonResidue(c, a *fe6) { + fp2, t := e.fp2, e.t + t[0].set(&a[0]) + fp2.mulByNonResidue(&c[0], &a[2]) + c[2].set(&a[1]) + c[1].set(t[0]) +} + +func (e *fp6) mulByBaseField(c, a *fe6, b *fe2) { + fp2 := e.fp2 + fp2.mul(&c[0], &a[0], b) + fp2.mul(&c[1], &a[1], b) + fp2.mul(&c[2], &a[2], b) +} + +func (e *fp6) exp(c, a *fe6, s *big.Int) { + z := e.one() + for i := s.BitLen() - 1; i >= 0; i-- { + e.square(z, z) + if s.Bit(i) == 1 { + e.mul(z, z, a) + } + } + c.set(z) +} + +func (e *fp6) inverse(c, a *fe6) { + fp2, t := e.fp2, e.t + fp2.square(t[0], &a[0]) + fp2.mul(t[1], &a[1], &a[2]) + fp2.mulByNonResidue(t[1], t[1]) + fp2.subAssign(t[0], t[1]) + fp2.square(t[1], &a[1]) + fp2.mul(t[2], &a[0], &a[2]) + fp2.subAssign(t[1], t[2]) + fp2.square(t[2], &a[2]) + fp2.mulByNonResidue(t[2], t[2]) + fp2.mul(t[3], &a[0], &a[1]) + fp2.subAssign(t[2], t[3]) + fp2.mul(t[3], &a[2], t[2]) + fp2.mul(t[4], &a[1], t[1]) + fp2.addAssign(t[3], t[4]) + fp2.mulByNonResidue(t[3], t[3]) + fp2.mul(t[4], &a[0], t[0]) + fp2.addAssign(t[3], t[4]) + fp2.inverse(t[3], t[3]) + fp2.mul(&c[0], t[0], t[3]) + fp2.mul(&c[1], t[2], t[3]) + fp2.mul(&c[2], t[1], t[3]) +} + +func (e *fp6) frobeniusMap(c, a *fe6, power uint) { + fp2 := e.fp2 + fp2.frobeniusMap(&c[0], &a[0], power) + fp2.frobeniusMap(&c[1], &a[1], power) + fp2.frobeniusMap(&c[2], &a[2], power) + switch power % 6 { + case 0: + return + case 3: + neg(&c[0][0], &a[1][1]) + c[1][1].set(&a[1][0]) + fp2.neg(&a[2], &a[2]) + default: + fp2.mul(&c[1], &c[1], &frobeniusCoeffs61[power%6]) + fp2.mul(&c[2], &c[2], &frobeniusCoeffs62[power%6]) + } +} + +func (e *fp6) frobeniusMapAssign(a *fe6, power uint) { + fp2 := e.fp2 + fp2.frobeniusMapAssign(&a[0], power) + fp2.frobeniusMapAssign(&a[1], power) + fp2.frobeniusMapAssign(&a[2], power) + t := e.t + switch power % 6 { + case 0: + return + case 3: + neg(&t[0][0], &a[1][1]) + a[1][1].set(&a[1][0]) + a[1][0].set(&t[0][0]) + fp2.neg(&a[2], &a[2]) + default: + fp2.mulAssign(&a[1], &frobeniusCoeffs61[power%6]) + fp2.mulAssign(&a[2], &frobeniusCoeffs62[power%6]) + } +} diff --git a/common/crypto/bls12381/fp_test.go b/common/crypto/bls12381/fp_test.go new file mode 100644 index 0000000..ba0a0f5 --- /dev/null +++ b/common/crypto/bls12381/fp_test.go @@ -0,0 +1,1412 @@ +package bls12381 + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +func TestFpSerialization(t *testing.T) { + t.Run("zero", func(t *testing.T) { + in := make([]byte, 48) + fe, err := fromBytes(in) + if err != nil { + t.Fatal(err) + } + if !fe.isZero() { + t.Fatal("bad serialization") + } + if !bytes.Equal(in, toBytes(fe)) { + t.Fatal("bad serialization") + } + }) + t.Run("bytes", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, err := fromBytes(toBytes(a)) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad serialization") + } + } + }) + t.Run("string", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, err := fromString(toString(a)) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad encoding or decoding") + } + } + }) + t.Run("big", func(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, err := fromBig(toBig(a)) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad encoding or decoding") + } + } + }) +} + +func TestFpAdditionCrossAgainstBigInt(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + c := new(fe) + big_a := toBig(a) + big_b := toBig(b) + big_c := new(big.Int) + add(c, a, b) + out_1 := toBytes(c) + out_2 := padBytes(big_c.Add(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied A") + } + double(c, a) + out_1 = toBytes(c) + out_2 = padBytes(big_c.Add(big_a, big_a).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied B") + } + sub(c, a, b) + out_1 = toBytes(c) + out_2 = padBytes(big_c.Sub(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied C") + } + neg(c, a) + out_1 = toBytes(c) + out_2 = padBytes(big_c.Neg(big_a).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied D") + } + } +} + +func TestFpAdditionCrossAgainstBigIntAssigned(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + big_a, big_b := toBig(a), toBig(b) + addAssign(a, b) + out_1 := toBytes(a) + out_2 := padBytes(big_a.Add(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied A") + } + a, _ = new(fe).rand(rand.Reader) + big_a = toBig(a) + doubleAssign(a) + out_1 = toBytes(a) + out_2 = padBytes(big_a.Add(big_a, big_a).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied B") + } + a, _ = new(fe).rand(rand.Reader) + b, _ = new(fe).rand(rand.Reader) + big_a, big_b = toBig(a), toBig(b) + subAssign(a, b) + out_1 = toBytes(a) + out_2 = padBytes(big_a.Sub(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied A") + } + } +} + +func TestFpAdditionProperties(t *testing.T) { + for i := 0; i < fuz; i++ { + zero := new(fe).zero() + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + c_1, c_2 := new(fe), new(fe) + add(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a + 0 == a") + } + sub(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a - 0 == a") + } + double(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("2 * 0 == 0") + } + neg(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("-0 == 0") + } + sub(c_1, zero, a) + neg(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("0-a == -a") + } + double(c_1, a) + add(c_2, a, a) + if !c_1.equal(c_2) { + t.Fatal("2 * a == a + a") + } + add(c_1, a, b) + add(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + sub(c_1, a, b) + sub(c_2, b, a) + neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + c_x, _ := new(fe).rand(rand.Reader) + add(c_1, a, b) + add(c_1, c_1, c_x) + add(c_2, a, c_x) + add(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a + b) + c == (a + c ) + b") + } + sub(c_1, a, b) + sub(c_1, c_1, c_x) + sub(c_2, a, c_x) + sub(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a - b) - c == (a - c ) -b") + } + } +} + +func TestFpAdditionPropertiesAssigned(t *testing.T) { + for i := 0; i < fuz; i++ { + zero := new(fe).zero() + a, b := new(fe), new(fe) + _, _ = a.rand(rand.Reader) + b.set(a) + addAssign(a, zero) + if !a.equal(b) { + t.Fatal("a + 0 == a") + } + subAssign(a, zero) + if !a.equal(b) { + t.Fatal("a - 0 == a") + } + a.set(zero) + doubleAssign(a) + if !a.equal(zero) { + t.Fatal("2 * 0 == 0") + } + a.set(zero) + subAssign(a, b) + neg(b, b) + if !a.equal(b) { + t.Fatal("0-a == -a") + } + _, _ = a.rand(rand.Reader) + b.set(a) + doubleAssign(a) + addAssign(b, b) + if !a.equal(b) { + t.Fatal("2 * a == a + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1, c_2 := new(fe).set(a), new(fe).set(b) + addAssign(c_1, b) + addAssign(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1.set(a) + c_2.set(b) + subAssign(c_1, b) + subAssign(c_2, a) + neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c, _ := new(fe).rand(rand.Reader) + a0 := new(fe).set(a) + addAssign(a, b) + addAssign(a, c) + addAssign(b, c) + addAssign(b, a0) + if !a.equal(b) { + t.Fatal("(a + b) + c == (b + c) + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + _, _ = c.rand(rand.Reader) + a0.set(a) + subAssign(a, b) + subAssign(a, c) + subAssign(a0, c) + subAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a - b) - c == (a - c) -b") + } + } +} + +func TestFpLazyOperations(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + c, _ := new(fe).rand(rand.Reader) + c0 := new(fe) + c1 := new(fe) + ladd(c0, a, b) + add(c1, a, b) + mul(c0, c0, c) + mul(c1, c1, c) + if !c0.equal(c1) { + // l+ operator stands for lazy addition + t.Fatal("(a + b) * c == (a l+ b) * c") + } + _, _ = a.rand(rand.Reader) + b.set(a) + ldouble(a, a) + ladd(b, b, b) + if !a.equal(b) { + t.Fatal("2 l* a = a l+ a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + _, _ = c.rand(rand.Reader) + a0 := new(fe).set(a) + lsubAssign(a, b) + laddAssign(a, &modulus) + mul(a, a, c) + subAssign(a0, b) + mul(a0, a0, c) + if !a.equal(a0) { + t.Fatal("((a l- b) + p) * c = (a-b) * c") + } + } +} + +func TestFpMultiplicationCrossAgainstBigInt(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + c := new(fe) + big_a := toBig(a) + big_b := toBig(b) + big_c := new(big.Int) + mul(c, a, b) + out_1 := toBytes(c) + out_2 := padBytes(big_c.Mul(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { + t.Fatal("cross test against big.Int is not satisfied") + } + } +} + +func TestFpMultiplicationProperties(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + zero, one := new(fe).zero(), new(fe).one() + c_1, c_2 := new(fe), new(fe) + mul(c_1, a, zero) + if !c_1.equal(zero) { + t.Fatal("a * 0 == 0") + } + mul(c_1, a, one) + if !c_1.equal(a) { + t.Fatal("a * 1 == a") + } + mul(c_1, a, b) + mul(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a * b == b * a") + } + c_x, _ := new(fe).rand(rand.Reader) + mul(c_1, a, b) + mul(c_1, c_1, c_x) + mul(c_2, c_x, b) + mul(c_2, c_2, a) + if !c_1.equal(c_2) { + t.Fatal("(a * b) * c == (a * c) * b") + } + square(a, zero) + if !a.equal(zero) { + t.Fatal("0^2 == 0") + } + square(a, one) + if !a.equal(one) { + t.Fatal("1^2 == 1") + } + _, _ = a.rand(rand.Reader) + square(c_1, a) + mul(c_2, a, a) + if !c_1.equal(c_1) { + t.Fatal("a^2 == a*a") + } + } +} + +func TestFpExponentiation(t *testing.T) { + for i := 0; i < fuz; i++ { + a, _ := new(fe).rand(rand.Reader) + u := new(fe) + exp(u, a, big.NewInt(0)) + if !u.isOne() { + t.Fatal("a^0 == 1") + } + exp(u, a, big.NewInt(1)) + if !u.equal(a) { + t.Fatal("a^1 == a") + } + v := new(fe) + mul(u, a, a) + mul(u, u, u) + mul(u, u, u) + exp(v, a, big.NewInt(8)) + if !u.equal(v) { + t.Fatal("((a^2)^2)^2 == a^8") + } + p := modulus.big() + exp(u, a, p) + if !u.equal(a) { + t.Fatal("a^p == a") + } + exp(u, a, p.Sub(p, big.NewInt(1))) + if !u.isOne() { + t.Fatal("a^(p-1) == 1") + } + } +} + +func TestFpInversion(t *testing.T) { + for i := 0; i < fuz; i++ { + u := new(fe) + zero, one := new(fe).zero(), new(fe).one() + inverse(u, zero) + if !u.equal(zero) { + t.Fatal("(0^-1) == 0)") + } + inverse(u, one) + if !u.equal(one) { + t.Fatal("(1^-1) == 1)") + } + a, _ := new(fe).rand(rand.Reader) + inverse(u, a) + mul(u, u, a) + if !u.equal(one) { + t.Fatal("(r*a) * r*(a^-1) == r)") + } + v := new(fe) + p := modulus.big() + exp(u, a, p.Sub(p, big.NewInt(2))) + inverse(v, a) + if !v.equal(u) { + t.Fatal("a^(p-2) == a^-1") + } + } +} + +//func TestFpSquareRoot(t *testing.T) { +// r := new(fe) +// if sqrt(r, nonResidue1) { +// t.Fatal("non residue cannot have a sqrt") +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe).rand(rand.Reader) +// aa, rr, r := &fe{}, &fe{}, &fe{} +// square(aa, a) +// if !sqrt(r, aa) { +// t.Fatal("bad sqrt 1") +// } +// square(rr, r) +// if !rr.equal(aa) { +// t.Fatal("bad sqrt 2") +// } +// } +//} + +//func TestFpNonResidue(t *testing.T) { +// if !isQuadraticNonResidue(nonResidue1) { +// t.Fatal("element is quadratic non residue, 1") +// } +// if isQuadraticNonResidue(new(fe).one()) { +// t.Fatal("one is not quadratic non residue") +// } +// if !isQuadraticNonResidue(new(fe).zero()) { +// t.Fatal("should accept zero as quadratic non residue") +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe).rand(rand.Reader) +// square(a, a) +// if isQuadraticNonResidue(new(fe).one()) { +// t.Fatal("element is not quadratic non residue") +// } +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe).rand(rand.Reader) +// if !sqrt(new(fe), a) { +// if !isQuadraticNonResidue(a) { +// t.Fatal("element is quadratic non residue, 2", i) +// } +// } else { +// i-- +// } +// } +// +//} + +//func TestFp2Serialization(t *testing.T) { +// field := newFp2() +// for i := 0; i < fuz; i++ { +// a, _ := new(fe2).rand(rand.Reader) +// b, err := field.fromBytes(field.toBytes(a)) +// if err != nil { +// t.Fatal(err) +// } +// if !a.equal(b) { +// t.Fatal("bad serialization") +// } +// } +//} + +func TestFp2AdditionProperties(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + zero := field.zero() + a, _ := new(fe2).rand(rand.Reader) + b, _ := new(fe2).rand(rand.Reader) + c_1 := field.new() + c_2 := field.new() + field.add(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a + 0 == a") + } + field.sub(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a - 0 == a") + } + field.double(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("2 * 0 == 0") + } + field.neg(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("-0 == 0") + } + field.sub(c_1, zero, a) + field.neg(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("0-a == -a") + } + field.double(c_1, a) + field.add(c_2, a, a) + if !c_1.equal(c_2) { + t.Fatal("2 * a == a + a") + } + field.add(c_1, a, b) + field.add(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + field.sub(c_1, a, b) + field.sub(c_2, b, a) + field.neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + c_x, _ := new(fe2).rand(rand.Reader) + field.add(c_1, a, b) + field.add(c_1, c_1, c_x) + field.add(c_2, a, c_x) + field.add(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a + b) + c == (a + c ) + b") + } + field.sub(c_1, a, b) + field.sub(c_1, c_1, c_x) + field.sub(c_2, a, c_x) + field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a - b) - c == (a - c ) -b") + } + } +} + +func TestFp2AdditionPropertiesAssigned(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + zero := new(fe2).zero() + a, b := new(fe2), new(fe2) + _, _ = a.rand(rand.Reader) + b.set(a) + field.addAssign(a, zero) + if !a.equal(b) { + t.Fatal("a + 0 == a") + } + field.subAssign(a, zero) + if !a.equal(b) { + t.Fatal("a - 0 == a") + } + a.set(zero) + field.doubleAssign(a) + if !a.equal(zero) { + t.Fatal("2 * 0 == 0") + } + a.set(zero) + field.subAssign(a, b) + field.neg(b, b) + if !a.equal(b) { + t.Fatal("0-a == -a") + } + _, _ = a.rand(rand.Reader) + b.set(a) + field.doubleAssign(a) + field.addAssign(b, b) + if !a.equal(b) { + t.Fatal("2 * a == a + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1, c_2 := new(fe2).set(a), new(fe2).set(b) + field.addAssign(c_1, b) + field.addAssign(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1.set(a) + c_2.set(b) + field.subAssign(c_1, b) + field.subAssign(c_2, a) + field.neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c, _ := new(fe2).rand(rand.Reader) + a0 := new(fe2).set(a) + field.addAssign(a, b) + field.addAssign(a, c) + field.addAssign(b, c) + field.addAssign(b, a0) + if !a.equal(b) { + t.Fatal("(a + b) + c == (b + c) + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + _, _ = c.rand(rand.Reader) + a0.set(a) + field.subAssign(a, b) + field.subAssign(a, c) + field.subAssign(a0, c) + field.subAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a - b) - c == (a - c) -b") + } + } +} + +func TestFp2LazyOperations(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + a, _ := new(fe2).rand(rand.Reader) + b, _ := new(fe2).rand(rand.Reader) + c, _ := new(fe2).rand(rand.Reader) + c0 := new(fe2) + c1 := new(fe2) + field.ladd(c0, a, b) + field.add(c1, a, b) + field.mulAssign(c0, c) + field.mulAssign(c1, c) + if !c0.equal(c1) { + // l+ operator stands for lazy addition + t.Fatal("(a + b) * c == (a l+ b) * c") + } + _, _ = a.rand(rand.Reader) + b.set(a) + field.ldouble(a, a) + field.ladd(b, b, b) + if !a.equal(b) { + t.Fatal("2 l* a = a l+ a") + } + } +} + +func TestFp2MultiplicationProperties(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + a, _ := new(fe2).rand(rand.Reader) + b, _ := new(fe2).rand(rand.Reader) + zero := field.zero() + one := field.one() + c_1, c_2 := field.new(), field.new() + field.mul(c_1, a, zero) + if !c_1.equal(zero) { + t.Fatal("a * 0 == 0") + } + field.mul(c_1, a, one) + if !c_1.equal(a) { + t.Fatal("a * 1 == a") + } + field.mul(c_1, a, b) + field.mul(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a * b == b * a") + } + c_x, _ := new(fe2).rand(rand.Reader) + field.mul(c_1, a, b) + field.mul(c_1, c_1, c_x) + field.mul(c_2, c_x, b) + field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { + t.Fatal("(a * b) * c == (a * c) * b") + } + field.square(a, zero) + if !a.equal(zero) { + t.Fatal("0^2 == 0") + } + field.square(a, one) + if !a.equal(one) { + t.Fatal("1^2 == 1") + } + _, _ = a.rand(rand.Reader) + field.square(c_1, a) + field.mul(c_2, a, a) + if !c_2.equal(c_1) { + t.Fatal("a^2 == a*a") + } + } +} + +func TestFp2MultiplicationPropertiesAssigned(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + a, _ := new(fe2).rand(rand.Reader) + zero, one := new(fe2).zero(), new(fe2).one() + field.mulAssign(a, zero) + if !a.equal(zero) { + t.Fatal("a * 0 == 0") + } + _, _ = a.rand(rand.Reader) + a0 := new(fe2).set(a) + field.mulAssign(a, one) + if !a.equal(a0) { + t.Fatal("a * 1 == a") + } + _, _ = a.rand(rand.Reader) + b, _ := new(fe2).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(b, a0) + if !a.equal(b) { + t.Fatal("a * b == b * a") + } + c, _ := new(fe2).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(a, c) + field.mulAssign(a0, c) + field.mulAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a * b) * c == (a * c) * b") + } + a0.set(a) + field.squareAssign(a) + field.mulAssign(a0, a0) + if !a.equal(a0) { + t.Fatal("a^2 == a*a") + } + } +} + +func TestFp2Exponentiation(t *testing.T) { + field := newFp2() + for i := 0; i < fuz; i++ { + a, _ := new(fe2).rand(rand.Reader) + u := field.new() + field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { + t.Fatal("a^0 == 1") + } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { + t.Fatal("a^1 == a") + } + v := field.new() + field.mul(u, a, a) + field.mul(u, u, u) + field.mul(u, u, u) + field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { + t.Fatal("((a^2)^2)^2 == a^8") + } + } +} + +func TestFp2Inversion(t *testing.T) { + field := newFp2() + u := field.new() + zero := field.zero() + one := field.one() + field.inverse(u, zero) + if !u.equal(zero) { + t.Fatal("(0 ^ -1) == 0)") + } + field.inverse(u, one) + if !u.equal(one) { + t.Fatal("(1 ^ -1) == 1)") + } + for i := 0; i < fuz; i++ { + a, _ := new(fe2).rand(rand.Reader) + field.inverse(u, a) + field.mul(u, u, a) + if !u.equal(one) { + t.Fatal("(r * a) * r * (a ^ -1) == r)") + } + } +} + +//func TestFp2SquareRoot(t *testing.T) { +// field := newFp2() +// for z := 0; z < 1000; z++ { +// zi := new(fe) +// sub(zi, &modulus, &fe{uint64(z * z)}) +// // r = (-z*z, 0) +// r := &fe2{*zi, fe{0}} +// toMont(&r[0], &r[0]) +// toMont(&r[1], &r[1]) +// c := field.new() +// // sqrt((-z*z, 0)) = (0, z) +// if !field.sqrt(c, r) { +// t.Fatal("z*z does have a square root") +// } +// e := &fe2{fe{uint64(0)}, fe{uint64(z)}} +// toMont(&e[0], &e[0]) +// toMont(&e[1], &e[1]) +// field.square(e, e) +// field.square(c, c) +// if !e.equal(c) { +// t.Fatal("square root failed") +// } +// } +// if field.sqrt(field.new(), nonResidue2) { +// t.Fatal("non residue cannot have a sqrt") +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe2).rand(rand.Reader) +// aa, rr, r := field.new(), field.new(), field.new() +// field.square(aa, a) +// if !field.sqrt(r, aa) { +// t.Fatal("bad sqrt 1") +// } +// field.square(rr, r) +// if !rr.equal(aa) { +// t.Fatal("bad sqrt 2") +// } +// } +//} + +//func TestFp2NonResidue(t *testing.T) { +// field := newFp2() +// if !field.isQuadraticNonResidue(nonResidue2) { +// t.Fatal("element is quadratic non residue, 1") +// } +// if field.isQuadraticNonResidue(new(fe2).one()) { +// t.Fatal("one is not quadratic non residue") +// } +// if !field.isQuadraticNonResidue(new(fe2).zero()) { +// t.Fatal("should accept zero as quadratic non residue") +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe2).rand(rand.Reader) +// field.squareAssign(a) +// if field.isQuadraticNonResidue(new(fe2).one()) { +// t.Fatal("element is not quadratic non residue") +// } +// } +// for i := 0; i < fuz; i++ { +// a, _ := new(fe2).rand(rand.Reader) +// if !field.sqrt(new(fe2), a) { +// if !field.isQuadraticNonResidue(a) { +// t.Fatal("element is quadratic non residue, 2", i) +// } +// } else { +// i-- +// } +// } +//} + +func TestFp6Serialization(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe6).rand(rand.Reader) + b, err := field.fromBytes(field.toBytes(a)) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad serialization") + } + } +} + +func TestFp6AdditionProperties(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + zero := field.zero() + a, _ := new(fe6).rand(rand.Reader) + b, _ := new(fe6).rand(rand.Reader) + c_1 := field.new() + c_2 := field.new() + field.add(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a + 0 == a") + } + field.sub(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a - 0 == a") + } + field.double(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("2 * 0 == 0") + } + field.neg(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("-0 == 0") + } + field.sub(c_1, zero, a) + field.neg(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("0-a == -a") + } + field.double(c_1, a) + field.add(c_2, a, a) + if !c_1.equal(c_2) { + t.Fatal("2 * a == a + a") + } + field.add(c_1, a, b) + field.add(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + field.sub(c_1, a, b) + field.sub(c_2, b, a) + field.neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + c_x, _ := new(fe6).rand(rand.Reader) + field.add(c_1, a, b) + field.add(c_1, c_1, c_x) + field.add(c_2, a, c_x) + field.add(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a + b) + c == (a + c ) + b") + } + field.sub(c_1, a, b) + field.sub(c_1, c_1, c_x) + field.sub(c_2, a, c_x) + field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a - b) - c == (a - c ) -b") + } + } +} + +func TestFp6AdditionPropertiesAssigned(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + zero := new(fe6).zero() + a, b := new(fe6), new(fe6) + _, _ = a.rand(rand.Reader) + b.set(a) + field.addAssign(a, zero) + if !a.equal(b) { + t.Fatal("a + 0 == a") + } + field.subAssign(a, zero) + if !a.equal(b) { + t.Fatal("a - 0 == a") + } + a.set(zero) + field.doubleAssign(a) + if !a.equal(zero) { + t.Fatal("2 * 0 == 0") + } + a.set(zero) + field.subAssign(a, b) + field.neg(b, b) + if !a.equal(b) { + t.Fatal("0-a == -a") + } + _, _ = a.rand(rand.Reader) + b.set(a) + field.doubleAssign(a) + field.addAssign(b, b) + if !a.equal(b) { + t.Fatal("2 * a == a + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1, c_2 := new(fe6).set(a), new(fe6).set(b) + field.addAssign(c_1, b) + field.addAssign(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c_1.set(a) + c_2.set(b) + field.subAssign(c_1, b) + field.subAssign(c_2, a) + field.neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + c, _ := new(fe6).rand(rand.Reader) + a0 := new(fe6).set(a) + field.addAssign(a, b) + field.addAssign(a, c) + field.addAssign(b, c) + field.addAssign(b, a0) + if !a.equal(b) { + t.Fatal("(a + b) + c == (b + c) + a") + } + _, _ = a.rand(rand.Reader) + _, _ = b.rand(rand.Reader) + _, _ = c.rand(rand.Reader) + a0.set(a) + field.subAssign(a, b) + field.subAssign(a, c) + field.subAssign(a0, c) + field.subAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a - b) - c == (a - c) -b") + } + } +} + +func TestFp6SparseMultiplication(t *testing.T) { + fp6 := newFp6(nil) + var a, b, u *fe6 + for j := 0; j < fuz; j++ { + a, _ = new(fe6).rand(rand.Reader) + b, _ = new(fe6).rand(rand.Reader) + u, _ = new(fe6).rand(rand.Reader) + b[2].zero() + fp6.mul(u, a, b) + fp6.mulBy01(a, a, &b[0], &b[1]) + if !a.equal(u) { + t.Fatal("bad mul by 01") + } + } + for j := 0; j < fuz; j++ { + a, _ = new(fe6).rand(rand.Reader) + b, _ = new(fe6).rand(rand.Reader) + u, _ = new(fe6).rand(rand.Reader) + b[2].zero() + b[0].zero() + fp6.mul(u, a, b) + fp6.mulBy1(a, a, &b[1]) + if !a.equal(u) { + t.Fatal("bad mul by 1") + } + } +} + +func TestFp6MultiplicationProperties(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe6).rand(rand.Reader) + b, _ := new(fe6).rand(rand.Reader) + zero := field.zero() + one := field.one() + c_1, c_2 := field.new(), field.new() + field.mul(c_1, a, zero) + if !c_1.equal(zero) { + t.Fatal("a * 0 == 0") + } + field.mul(c_1, a, one) + if !c_1.equal(a) { + t.Fatal("a * 1 == a") + } + field.mul(c_1, a, b) + field.mul(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a * b == b * a") + } + c_x, _ := new(fe6).rand(rand.Reader) + field.mul(c_1, a, b) + field.mul(c_1, c_1, c_x) + field.mul(c_2, c_x, b) + field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { + t.Fatal("(a * b) * c == (a * c) * b") + } + field.square(a, zero) + if !a.equal(zero) { + t.Fatal("0^2 == 0") + } + field.square(a, one) + if !a.equal(one) { + t.Fatal("1^2 == 1") + } + _, _ = a.rand(rand.Reader) + field.square(c_1, a) + field.mul(c_2, a, a) + if !c_2.equal(c_1) { + t.Fatal("a^2 == a*a") + } + } +} + +func TestFp6MultiplicationPropertiesAssigned(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe6).rand(rand.Reader) + zero, one := new(fe6).zero(), new(fe6).one() + field.mulAssign(a, zero) + if !a.equal(zero) { + t.Fatal("a * 0 == 0") + } + _, _ = a.rand(rand.Reader) + a0 := new(fe6).set(a) + field.mulAssign(a, one) + if !a.equal(a0) { + t.Fatal("a * 1 == a") + } + _, _ = a.rand(rand.Reader) + b, _ := new(fe6).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(b, a0) + if !a.equal(b) { + t.Fatal("a * b == b * a") + } + c, _ := new(fe6).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(a, c) + field.mulAssign(a0, c) + field.mulAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a * b) * c == (a * c) * b") + } + } +} + +func TestFp6Exponentiation(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe6).rand(rand.Reader) + u := field.new() + field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { + t.Fatal("a^0 == 1") + } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { + t.Fatal("a^1 == a") + } + v := field.new() + field.mul(u, a, a) + field.mul(u, u, u) + field.mul(u, u, u) + field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { + t.Fatal("((a^2)^2)^2 == a^8") + } + } +} + +func TestFp6Inversion(t *testing.T) { + field := newFp6(nil) + for i := 0; i < fuz; i++ { + u := field.new() + zero := field.zero() + one := field.one() + field.inverse(u, zero) + if !u.equal(zero) { + t.Fatal("(0^-1) == 0)") + } + field.inverse(u, one) + if !u.equal(one) { + t.Fatal("(1^-1) == 1)") + } + a, _ := new(fe6).rand(rand.Reader) + field.inverse(u, a) + field.mul(u, u, a) + if !u.equal(one) { + t.Fatal("(r*a) * r*(a^-1) == r)") + } + } +} + +func TestFp12Serialization(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe12).rand(rand.Reader) + b, err := field.fromBytes(field.toBytes(a)) + if err != nil { + t.Fatal(err) + } + if !a.equal(b) { + t.Fatal("bad serialization") + } + } +} + +func TestFp12AdditionProperties(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + zero := field.zero() + a, _ := new(fe12).rand(rand.Reader) + b, _ := new(fe12).rand(rand.Reader) + c_1 := field.new() + c_2 := field.new() + field.add(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a + 0 == a") + } + field.sub(c_1, a, zero) + if !c_1.equal(a) { + t.Fatal("a - 0 == a") + } + field.double(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("2 * 0 == 0") + } + field.neg(c_1, zero) + if !c_1.equal(zero) { + t.Fatal("-0 == 0") + } + field.sub(c_1, zero, a) + field.neg(c_2, a) + if !c_1.equal(c_2) { + t.Fatal("0-a == -a") + } + field.double(c_1, a) + field.add(c_2, a, a) + if !c_1.equal(c_2) { + t.Fatal("2 * a == a + a") + } + field.add(c_1, a, b) + field.add(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a + b = b + a") + } + field.sub(c_1, a, b) + field.sub(c_2, b, a) + field.neg(c_2, c_2) + if !c_1.equal(c_2) { + t.Fatal("a - b = - ( b - a )") + } + c_x, _ := new(fe12).rand(rand.Reader) + field.add(c_1, a, b) + field.add(c_1, c_1, c_x) + field.add(c_2, a, c_x) + field.add(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a + b) + c == (a + c ) + b") + } + field.sub(c_1, a, b) + field.sub(c_1, c_1, c_x) + field.sub(c_2, a, c_x) + field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { + t.Fatal("(a - b) - c == (a - c ) -b") + } + } +} + +func TestFp12MultiplicationProperties(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe12).rand(rand.Reader) + b, _ := new(fe12).rand(rand.Reader) + zero := field.zero() + one := field.one() + c_1, c_2 := field.new(), field.new() + field.mul(c_1, a, zero) + if !c_1.equal(zero) { + t.Fatal("a * 0 == 0") + } + field.mul(c_1, a, one) + if !c_1.equal(a) { + t.Fatal("a * 1 == a") + } + field.mul(c_1, a, b) + field.mul(c_2, b, a) + if !c_1.equal(c_2) { + t.Fatal("a * b == b * a") + } + c_x, _ := new(fe12).rand(rand.Reader) + field.mul(c_1, a, b) + field.mul(c_1, c_1, c_x) + field.mul(c_2, c_x, b) + field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { + t.Fatal("(a * b) * c == (a * c) * b") + } + field.square(a, zero) + if !a.equal(zero) { + t.Fatal("0^2 == 0") + } + field.square(a, one) + if !a.equal(one) { + t.Fatal("1^2 == 1") + } + _, _ = a.rand(rand.Reader) + field.square(c_1, a) + field.mul(c_2, a, a) + if !c_2.equal(c_1) { + t.Fatal("a^2 == a*a") + } + } +} + +func TestFp12MultiplicationPropertiesAssigned(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe12).rand(rand.Reader) + zero, one := new(fe12).zero(), new(fe12).one() + field.mulAssign(a, zero) + if !a.equal(zero) { + t.Fatal("a * 0 == 0") + } + _, _ = a.rand(rand.Reader) + a0 := new(fe12).set(a) + field.mulAssign(a, one) + if !a.equal(a0) { + t.Fatal("a * 1 == a") + } + _, _ = a.rand(rand.Reader) + b, _ := new(fe12).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(b, a0) + if !a.equal(b) { + t.Fatal("a * b == b * a") + } + c, _ := new(fe12).rand(rand.Reader) + a0.set(a) + field.mulAssign(a, b) + field.mulAssign(a, c) + field.mulAssign(a0, c) + field.mulAssign(a0, b) + if !a.equal(a0) { + t.Fatal("(a * b) * c == (a * c) * b") + } + } +} + +func TestFp12SparseMultiplication(t *testing.T) { + fp12 := newFp12(nil) + var a, b, u *fe12 + for j := 0; j < fuz; j++ { + a, _ = new(fe12).rand(rand.Reader) + b, _ = new(fe12).rand(rand.Reader) + u, _ = new(fe12).rand(rand.Reader) + b[0][2].zero() + b[1][0].zero() + b[1][2].zero() + fp12.mul(u, a, b) + fp12.mulBy014Assign(a, &b[0][0], &b[0][1], &b[1][1]) + if !a.equal(u) { + t.Fatal("bad mul by 01") + } + } +} + +func TestFp12Exponentiation(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + a, _ := new(fe12).rand(rand.Reader) + u := field.new() + field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { + t.Fatal("a^0 == 1") + } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { + t.Fatal("a^1 == a") + } + v := field.new() + field.mul(u, a, a) + field.mul(u, u, u) + field.mul(u, u, u) + field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { + t.Fatal("((a^2)^2)^2 == a^8") + } + } +} + +func TestFp12Inversion(t *testing.T) { + field := newFp12(nil) + for i := 0; i < fuz; i++ { + u := field.new() + zero := field.zero() + one := field.one() + field.inverse(u, zero) + if !u.equal(zero) { + t.Fatal("(0^-1) == 0)") + } + field.inverse(u, one) + if !u.equal(one) { + t.Fatal("(1^-1) == 1)") + } + a, _ := new(fe12).rand(rand.Reader) + field.inverse(u, a) + field.mul(u, u, a) + if !u.equal(one) { + t.Fatal("(r*a) * r*(a^-1) == r)") + } + } +} + +func BenchmarkMultiplication(t *testing.B) { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + c, _ := new(fe).rand(rand.Reader) + t.ResetTimer() + for i := 0; i < t.N; i++ { + mul(c, a, b) + } +} + +func BenchmarkInverse(t *testing.B) { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + t.ResetTimer() + for i := 0; i < t.N; i++ { + inverse(a, b) + } +} + +func padBytes(in []byte, size int) []byte { + out := make([]byte, size) + if len(in) > size { + panic("bad input for padding") + } + copy(out[size-len(in):], in) + return out +} diff --git a/common/crypto/bls12381/g1.go b/common/crypto/bls12381/g1.go new file mode 100644 index 0000000..dd867c2 --- /dev/null +++ b/common/crypto/bls12381/g1.go @@ -0,0 +1,435 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//nolint:golint +package bls12381 + +import ( + "errors" + "math" + "math/big" +) + +// PointG1 is type for point in G1. +// PointG1 is both used for Affine and Jacobian point representation. +// If z is equal to one the point is considered as in affine form. +type PointG1 [3]fe + +func (p *PointG1) Set(p2 *PointG1) *PointG1 { + p[0].set(&p2[0]) + p[1].set(&p2[1]) + p[2].set(&p2[2]) + return p +} + +// Zero returns G1 point in point at infinity representation +func (p *PointG1) Zero() *PointG1 { + p[0].zero() + p[1].one() + p[2].zero() + return p +} + +type tempG1 struct { + t [9]*fe +} + +// G1 is struct for G1 group. +type G1 struct { + tempG1 +} + +// NewG1 constructs a new G1 instance. +func NewG1() *G1 { + t := newTempG1() + return &G1{t} +} + +func newTempG1() tempG1 { + t := [9]*fe{} + for i := 0; i < 9; i++ { + t[i] = &fe{} + } + return tempG1{t} +} + +// Q returns group order in big.Int. +func (g *G1) Q() *big.Int { + return new(big.Int).Set(q) +} + +func (g *G1) fromBytesUnchecked(in []byte) (*PointG1, error) { + p0, err := fromBytes(in[:48]) + if err != nil { + return nil, err + } + p1, err := fromBytes(in[48:]) + if err != nil { + return nil, err + } + p2 := new(fe).one() + return &PointG1{*p0, *p1, *p2}, nil +} + +// FromBytes constructs a new point given uncompressed byte input. +// FromBytes does not take zcash flags into account. +// Byte input expected to be larger than 96 bytes. +// First 96 bytes should be concatenation of x and y values. +// Point (0, 0) is considered as infinity. +func (g *G1) FromBytes(in []byte) (*PointG1, error) { + if len(in) != 96 { + return nil, errors.New("input string should be equal or larger than 96") + } + p0, err := fromBytes(in[:48]) + if err != nil { + return nil, err + } + p1, err := fromBytes(in[48:]) + if err != nil { + return nil, err + } + // check if given input points to infinity + if p0.isZero() && p1.isZero() { + return g.Zero(), nil + } + p2 := new(fe).one() + p := &PointG1{*p0, *p1, *p2} + if !g.IsOnCurve(p) { + return nil, errors.New("point is not on curve") + } + return p, nil +} + +// DecodePoint given encoded (x, y) coordinates in 128 bytes returns a valid G1 Point. +func (g *G1) DecodePoint(in []byte) (*PointG1, error) { + if len(in) != 128 { + return nil, errors.New("invalid g1 point length") + } + pointBytes := make([]byte, 96) + // decode x + xBytes, err := decodeFieldElement(in[:64]) + if err != nil { + return nil, err + } + // decode y + yBytes, err := decodeFieldElement(in[64:]) + if err != nil { + return nil, err + } + copy(pointBytes[:48], xBytes) + copy(pointBytes[48:], yBytes) + return g.FromBytes(pointBytes) +} + +// ToBytes serializes a point into bytes in uncompressed form. +// ToBytes does not take zcash flags into account. +// ToBytes returns (0, 0) if point is infinity. +func (g *G1) ToBytes(p *PointG1) []byte { + out := make([]byte, 96) + if g.IsZero(p) { + return out + } + g.Affine(p) + copy(out[:48], toBytes(&p[0])) + copy(out[48:], toBytes(&p[1])) + return out +} + +// EncodePoint encodes a point into 128 bytes. +func (g *G1) EncodePoint(p *PointG1) []byte { + outRaw := g.ToBytes(p) + out := make([]byte, 128) + // encode x + copy(out[16:], outRaw[:48]) + // encode y + copy(out[64+16:], outRaw[48:]) + return out +} + +// New creates a new G1 Point which is equal to zero in other words point at infinity. +func (g *G1) New() *PointG1 { + return g.Zero() +} + +// Zero returns a new G1 Point which is equal to point at infinity. +func (g *G1) Zero() *PointG1 { + return new(PointG1).Zero() +} + +// One returns a new G1 Point which is equal to generator point. +func (g *G1) One() *PointG1 { + p := &PointG1{} + return p.Set(&g1One) +} + +// IsZero returns true if given point is equal to zero. +func (g *G1) IsZero(p *PointG1) bool { + return p[2].isZero() +} + +// Equal checks if given two G1 point is equal in their affine form. +func (g *G1) Equal(p1, p2 *PointG1) bool { + if g.IsZero(p1) { + return g.IsZero(p2) + } + if g.IsZero(p2) { + return g.IsZero(p1) + } + t := g.t + square(t[0], &p1[2]) + square(t[1], &p2[2]) + mul(t[2], t[0], &p2[0]) + mul(t[3], t[1], &p1[0]) + mul(t[0], t[0], &p1[2]) + mul(t[1], t[1], &p2[2]) + mul(t[1], t[1], &p1[1]) + mul(t[0], t[0], &p2[1]) + return t[0].equal(t[1]) && t[2].equal(t[3]) +} + +// InCorrectSubgroup checks whether given point is in correct subgroup. +func (g *G1) InCorrectSubgroup(p *PointG1) bool { + tmp := &PointG1{} + g.MulScalar(tmp, p, q) + return g.IsZero(tmp) +} + +// IsOnCurve checks a G1 point is on curve. +func (g *G1) IsOnCurve(p *PointG1) bool { + if g.IsZero(p) { + return true + } + t := g.t + square(t[0], &p[1]) + square(t[1], &p[0]) + mul(t[1], t[1], &p[0]) + square(t[2], &p[2]) + square(t[3], t[2]) + mul(t[2], t[2], t[3]) + mul(t[2], b, t[2]) + add(t[1], t[1], t[2]) + return t[0].equal(t[1]) +} + +// IsAffine checks a G1 point whether it is in affine form. +func (g *G1) IsAffine(p *PointG1) bool { + return p[2].isOne() +} + +// Add adds two G1 points p1, p2 and assigns the result to point at first argument. +func (g *G1) Affine(p *PointG1) *PointG1 { + if g.IsZero(p) { + return p + } + if !g.IsAffine(p) { + t := g.t + inverse(t[0], &p[2]) + square(t[1], t[0]) + mul(&p[0], &p[0], t[1]) + mul(t[0], t[0], t[1]) + mul(&p[1], &p[1], t[0]) + p[2].one() + } + return p +} + +// Add adds two G1 points p1, p2 and assigns the result to point at first argument. +func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { + // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#addition-add-2007-bl + if g.IsZero(p1) { + return r.Set(p2) + } + if g.IsZero(p2) { + return r.Set(p1) + } + t := g.t + square(t[7], &p1[2]) + mul(t[1], &p2[0], t[7]) + mul(t[2], &p1[2], t[7]) + mul(t[0], &p2[1], t[2]) + square(t[8], &p2[2]) + mul(t[3], &p1[0], t[8]) + mul(t[4], &p2[2], t[8]) + mul(t[2], &p1[1], t[4]) + if t[1].equal(t[3]) { + if t[0].equal(t[2]) { + return g.Double(r, p1) + } + return r.Zero() + } + sub(t[1], t[1], t[3]) + double(t[4], t[1]) + square(t[4], t[4]) + mul(t[5], t[1], t[4]) + sub(t[0], t[0], t[2]) + double(t[0], t[0]) + square(t[6], t[0]) + sub(t[6], t[6], t[5]) + mul(t[3], t[3], t[4]) + double(t[4], t[3]) + sub(&r[0], t[6], t[4]) + sub(t[4], t[3], &r[0]) + mul(t[6], t[2], t[5]) + double(t[6], t[6]) + mul(t[0], t[0], t[4]) + sub(&r[1], t[0], t[6]) + add(t[0], &p1[2], &p2[2]) + square(t[0], t[0]) + sub(t[0], t[0], t[7]) + sub(t[0], t[0], t[8]) + mul(&r[2], t[0], t[1]) + return r +} + +// Double doubles a G1 point p and assigns the result to the point at first argument. +func (g *G1) Double(r, p *PointG1) *PointG1 { + // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + if g.IsZero(p) { + return r.Set(p) + } + t := g.t + square(t[0], &p[0]) + square(t[1], &p[1]) + square(t[2], t[1]) + add(t[1], &p[0], t[1]) + square(t[1], t[1]) + sub(t[1], t[1], t[0]) + sub(t[1], t[1], t[2]) + double(t[1], t[1]) + double(t[3], t[0]) + add(t[0], t[3], t[0]) + square(t[4], t[0]) + double(t[3], t[1]) + sub(&r[0], t[4], t[3]) + sub(t[1], t[1], &r[0]) + double(t[2], t[2]) + double(t[2], t[2]) + double(t[2], t[2]) + mul(t[0], t[0], t[1]) + sub(t[1], t[0], t[2]) + mul(t[0], &p[1], &p[2]) + r[1].set(t[1]) + double(&r[2], t[0]) + return r +} + +// Neg negates a G1 point p and assigns the result to the point at first argument. +func (g *G1) Neg(r, p *PointG1) *PointG1 { + r[0].set(&p[0]) + r[2].set(&p[2]) + neg(&r[1], &p[1]) + return r +} + +// Sub subtracts two G1 points p1, p2 and assigns the result to point at first argument. +func (g *G1) Sub(c, a, b *PointG1) *PointG1 { + d := &PointG1{} + g.Neg(d, b) + g.Add(c, a, d) + return c +} + +// MulScalar multiplies a point by given scalar value in big.Int and assigns the result to point at first argument. +func (g *G1) MulScalar(c, p *PointG1, e *big.Int) *PointG1 { + q, n := &PointG1{}, &PointG1{} + n.Set(p) + l := e.BitLen() + for i := 0; i < l; i++ { + if e.Bit(i) == 1 { + g.Add(q, q, n) + } + g.Double(n, n) + } + return c.Set(q) +} + +// ClearCofactor maps given a G1 point to correct subgroup +func (g *G1) ClearCofactor(p *PointG1) { + g.MulScalar(p, p, cofactorEFFG1) +} + +// MultiExp calculates multi exponentiation. Given pairs of G1 point and scalar values +// (P_0, e_0), (P_1, e_1), ... (P_n, e_n) calculates r = e_0 * P_0 + e_1 * P_1 + ... + e_n * P_n +// Length of points and scalars are expected to be equal, otherwise an error is returned. +// Result is assigned to point at first argument. +func (g *G1) MultiExp(r *PointG1, points []*PointG1, powers []*big.Int) (*PointG1, error) { + if len(points) != len(powers) { + return nil, errors.New("point and scalar vectors should be in same length") + } + var c uint32 = 3 + if len(powers) >= 32 { + c = uint32(math.Ceil(math.Log10(float64(len(powers))))) + } + bucketSize, numBits := (1<= 0; i-- { + g.Add(sum, sum, bucket[i]) + g.Add(acc, acc, sum) + } + windows[j] = g.New() + windows[j].Set(acc) + j++ + cur += c + } + acc.Zero() + for i := len(windows) - 1; i >= 0; i-- { + for j := uint32(0); j < c; j++ { + g.Double(acc, acc) + } + g.Add(acc, acc, windows[i]) + } + return r.Set(acc), nil +} + +// MapToCurve given a byte slice returns a valid G1 point. +// This mapping function implements the Simplified Shallue-van de Woestijne-Ulas method. +// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06 +// Input byte slice should be a valid field element, otherwise an error is returned. +func (g *G1) MapToCurve(in []byte) (*PointG1, error) { + u, err := fromBytes(in) + if err != nil { + return nil, err + } + x, y := swuMapG1(u) + isogenyMapG1(x, y) + one := new(fe).one() + p := &PointG1{*x, *y, *one} + g.ClearCofactor(p) + return g.Affine(p), nil +} diff --git a/common/crypto/bls12381/g2.go b/common/crypto/bls12381/g2.go new file mode 100644 index 0000000..0b40b24 --- /dev/null +++ b/common/crypto/bls12381/g2.go @@ -0,0 +1,457 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//noling:golint +package bls12381 + +import ( + "errors" + "math" + "math/big" +) + +// PointG2 is type for point in G2. +// PointG2 is both used for Affine and Jacobian point representation. +// If z is equal to one the point is considered as in affine form. +type PointG2 [3]fe2 + +// Set copies valeus of one point to another. +func (p *PointG2) Set(p2 *PointG2) *PointG2 { + p[0].set(&p2[0]) + p[1].set(&p2[1]) + p[2].set(&p2[2]) + return p +} + +// Zero returns G2 point in point at infinity representation +func (p *PointG2) Zero() *PointG2 { + p[0].zero() + p[1].one() + p[2].zero() + return p + +} + +type tempG2 struct { + t [9]*fe2 +} + +// G2 is struct for G2 group. +type G2 struct { + f *fp2 + tempG2 +} + +// NewG2 constructs a new G2 instance. +func NewG2() *G2 { + return newG2(nil) +} + +func newG2(f *fp2) *G2 { + if f == nil { + f = newFp2() + } + t := newTempG2() + return &G2{f, t} +} + +func newTempG2() tempG2 { + t := [9]*fe2{} + for i := 0; i < 9; i++ { + t[i] = &fe2{} + } + return tempG2{t} +} + +// Q returns group order in big.Int. +func (g *G2) Q() *big.Int { + return new(big.Int).Set(q) +} + +func (g *G2) fromBytesUnchecked(in []byte) (*PointG2, error) { + p0, err := g.f.fromBytes(in[:96]) + if err != nil { + return nil, err + } + p1, err := g.f.fromBytes(in[96:]) + if err != nil { + return nil, err + } + p2 := new(fe2).one() + return &PointG2{*p0, *p1, *p2}, nil +} + +// FromBytes constructs a new point given uncompressed byte input. +// FromBytes does not take zcash flags into account. +// Byte input expected to be larger than 96 bytes. +// First 192 bytes should be concatenation of x and y values +// Point (0, 0) is considered as infinity. +func (g *G2) FromBytes(in []byte) (*PointG2, error) { + if len(in) != 192 { + return nil, errors.New("input string should be equal or larger than 192") + } + p0, err := g.f.fromBytes(in[:96]) + if err != nil { + return nil, err + } + p1, err := g.f.fromBytes(in[96:]) + if err != nil { + return nil, err + } + // check if given input points to infinity + if p0.isZero() && p1.isZero() { + return g.Zero(), nil + } + p2 := new(fe2).one() + p := &PointG2{*p0, *p1, *p2} + if !g.IsOnCurve(p) { + return nil, errors.New("point is not on curve") + } + return p, nil +} + +// DecodePoint given encoded (x, y) coordinates in 256 bytes returns a valid G1 Point. +func (g *G2) DecodePoint(in []byte) (*PointG2, error) { + if len(in) != 256 { + return nil, errors.New("invalid g2 point length") + } + pointBytes := make([]byte, 192) + x0Bytes, err := decodeFieldElement(in[:64]) + if err != nil { + return nil, err + } + x1Bytes, err := decodeFieldElement(in[64:128]) + if err != nil { + return nil, err + } + y0Bytes, err := decodeFieldElement(in[128:192]) + if err != nil { + return nil, err + } + y1Bytes, err := decodeFieldElement(in[192:]) + if err != nil { + return nil, err + } + copy(pointBytes[:48], x1Bytes) + copy(pointBytes[48:96], x0Bytes) + copy(pointBytes[96:144], y1Bytes) + copy(pointBytes[144:192], y0Bytes) + return g.FromBytes(pointBytes) +} + +// ToBytes serializes a point into bytes in uncompressed form, +// does not take zcash flags into account, +// returns (0, 0) if point is infinity. +func (g *G2) ToBytes(p *PointG2) []byte { + out := make([]byte, 192) + if g.IsZero(p) { + return out + } + g.Affine(p) + copy(out[:96], g.f.toBytes(&p[0])) + copy(out[96:], g.f.toBytes(&p[1])) + return out +} + +// EncodePoint encodes a point into 256 bytes. +func (g *G2) EncodePoint(p *PointG2) []byte { + // outRaw is 96 bytes + outRaw := g.ToBytes(p) + out := make([]byte, 256) + // encode x + copy(out[16:16+48], outRaw[48:96]) + copy(out[80:80+48], outRaw[:48]) + // encode y + copy(out[144:144+48], outRaw[144:]) + copy(out[208:208+48], outRaw[96:144]) + return out +} + +// New creates a new G2 Point which is equal to zero in other words point at infinity. +func (g *G2) New() *PointG2 { + return new(PointG2).Zero() +} + +// Zero returns a new G2 Point which is equal to point at infinity. +func (g *G2) Zero() *PointG2 { + return new(PointG2).Zero() +} + +// One returns a new G2 Point which is equal to generator point. +func (g *G2) One() *PointG2 { + p := &PointG2{} + return p.Set(&g2One) +} + +// IsZero returns true if given point is equal to zero. +func (g *G2) IsZero(p *PointG2) bool { + return p[2].isZero() +} + +// Equal checks if given two G2 point is equal in their affine form. +func (g *G2) Equal(p1, p2 *PointG2) bool { + if g.IsZero(p1) { + return g.IsZero(p2) + } + if g.IsZero(p2) { + return g.IsZero(p1) + } + t := g.t + g.f.square(t[0], &p1[2]) + g.f.square(t[1], &p2[2]) + g.f.mul(t[2], t[0], &p2[0]) + g.f.mul(t[3], t[1], &p1[0]) + g.f.mul(t[0], t[0], &p1[2]) + g.f.mul(t[1], t[1], &p2[2]) + g.f.mul(t[1], t[1], &p1[1]) + g.f.mul(t[0], t[0], &p2[1]) + return t[0].equal(t[1]) && t[2].equal(t[3]) +} + +// InCorrectSubgroup checks whether given point is in correct subgroup. +func (g *G2) InCorrectSubgroup(p *PointG2) bool { + tmp := &PointG2{} + g.MulScalar(tmp, p, q) + return g.IsZero(tmp) +} + +// IsOnCurve checks a G2 point is on curve. +func (g *G2) IsOnCurve(p *PointG2) bool { + if g.IsZero(p) { + return true + } + t := g.t + g.f.square(t[0], &p[1]) + g.f.square(t[1], &p[0]) + g.f.mul(t[1], t[1], &p[0]) + g.f.square(t[2], &p[2]) + g.f.square(t[3], t[2]) + g.f.mul(t[2], t[2], t[3]) + g.f.mul(t[2], b2, t[2]) + g.f.add(t[1], t[1], t[2]) + return t[0].equal(t[1]) +} + +// IsAffine checks a G2 point whether it is in affine form. +func (g *G2) IsAffine(p *PointG2) bool { + return p[2].isOne() +} + +// Affine calculates affine form of given G2 point. +func (g *G2) Affine(p *PointG2) *PointG2 { + if g.IsZero(p) { + return p + } + if !g.IsAffine(p) { + t := g.t + g.f.inverse(t[0], &p[2]) + g.f.square(t[1], t[0]) + g.f.mul(&p[0], &p[0], t[1]) + g.f.mul(t[0], t[0], t[1]) + g.f.mul(&p[1], &p[1], t[0]) + p[2].one() + } + return p +} + +// Add adds two G2 points p1, p2 and assigns the result to point at first argument. +func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { + // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#addition-add-2007-bl + if g.IsZero(p1) { + return r.Set(p2) + } + if g.IsZero(p2) { + return r.Set(p1) + } + t := g.t + g.f.square(t[7], &p1[2]) + g.f.mul(t[1], &p2[0], t[7]) + g.f.mul(t[2], &p1[2], t[7]) + g.f.mul(t[0], &p2[1], t[2]) + g.f.square(t[8], &p2[2]) + g.f.mul(t[3], &p1[0], t[8]) + g.f.mul(t[4], &p2[2], t[8]) + g.f.mul(t[2], &p1[1], t[4]) + if t[1].equal(t[3]) { + if t[0].equal(t[2]) { + return g.Double(r, p1) + } + return r.Zero() + } + g.f.sub(t[1], t[1], t[3]) + g.f.double(t[4], t[1]) + g.f.square(t[4], t[4]) + g.f.mul(t[5], t[1], t[4]) + g.f.sub(t[0], t[0], t[2]) + g.f.double(t[0], t[0]) + g.f.square(t[6], t[0]) + g.f.sub(t[6], t[6], t[5]) + g.f.mul(t[3], t[3], t[4]) + g.f.double(t[4], t[3]) + g.f.sub(&r[0], t[6], t[4]) + g.f.sub(t[4], t[3], &r[0]) + g.f.mul(t[6], t[2], t[5]) + g.f.double(t[6], t[6]) + g.f.mul(t[0], t[0], t[4]) + g.f.sub(&r[1], t[0], t[6]) + g.f.add(t[0], &p1[2], &p2[2]) + g.f.square(t[0], t[0]) + g.f.sub(t[0], t[0], t[7]) + g.f.sub(t[0], t[0], t[8]) + g.f.mul(&r[2], t[0], t[1]) + return r +} + +// Double doubles a G2 point p and assigns the result to the point at first argument. +func (g *G2) Double(r, p *PointG2) *PointG2 { + // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + if g.IsZero(p) { + return r.Set(p) + } + t := g.t + g.f.square(t[0], &p[0]) + g.f.square(t[1], &p[1]) + g.f.square(t[2], t[1]) + g.f.add(t[1], &p[0], t[1]) + g.f.square(t[1], t[1]) + g.f.sub(t[1], t[1], t[0]) + g.f.sub(t[1], t[1], t[2]) + g.f.double(t[1], t[1]) + g.f.double(t[3], t[0]) + g.f.add(t[0], t[3], t[0]) + g.f.square(t[4], t[0]) + g.f.double(t[3], t[1]) + g.f.sub(&r[0], t[4], t[3]) + g.f.sub(t[1], t[1], &r[0]) + g.f.double(t[2], t[2]) + g.f.double(t[2], t[2]) + g.f.double(t[2], t[2]) + g.f.mul(t[0], t[0], t[1]) + g.f.sub(t[1], t[0], t[2]) + g.f.mul(t[0], &p[1], &p[2]) + r[1].set(t[1]) + g.f.double(&r[2], t[0]) + return r +} + +// Neg negates a G2 point p and assigns the result to the point at first argument. +func (g *G2) Neg(r, p *PointG2) *PointG2 { + r[0].set(&p[0]) + g.f.neg(&r[1], &p[1]) + r[2].set(&p[2]) + return r +} + +// Sub subtracts two G2 points p1, p2 and assigns the result to point at first argument. +func (g *G2) Sub(c, a, b *PointG2) *PointG2 { + d := &PointG2{} + g.Neg(d, b) + g.Add(c, a, d) + return c +} + +// MulScalar multiplies a point by given scalar value in big.Int and assigns the result to point at first argument. +func (g *G2) MulScalar(c, p *PointG2, e *big.Int) *PointG2 { + q, n := &PointG2{}, &PointG2{} + n.Set(p) + l := e.BitLen() + for i := 0; i < l; i++ { + if e.Bit(i) == 1 { + g.Add(q, q, n) + } + g.Double(n, n) + } + return c.Set(q) +} + +// ClearCofactor maps given a G2 point to correct subgroup +func (g *G2) ClearCofactor(p *PointG2) { + g.MulScalar(p, p, cofactorEFFG2) +} + +// MultiExp calculates multi exponentiation. Given pairs of G2 point and scalar values +// (P_0, e_0), (P_1, e_1), ... (P_n, e_n) calculates r = e_0 * P_0 + e_1 * P_1 + ... + e_n * P_n +// Length of points and scalars are expected to be equal, otherwise an error is returned. +// Result is assigned to point at first argument. +func (g *G2) MultiExp(r *PointG2, points []*PointG2, powers []*big.Int) (*PointG2, error) { + if len(points) != len(powers) { + return nil, errors.New("point and scalar vectors should be in same length") + } + var c uint32 = 3 + if len(powers) >= 32 { + c = uint32(math.Ceil(math.Log10(float64(len(powers))))) + } + bucketSize, numBits := (1<= 0; i-- { + g.Add(sum, sum, bucket[i]) + g.Add(acc, acc, sum) + } + windows[j] = g.New() + windows[j].Set(acc) + j++ + cur += c + } + acc.Zero() + for i := len(windows) - 1; i >= 0; i-- { + for j := uint32(0); j < c; j++ { + g.Double(acc, acc) + } + g.Add(acc, acc, windows[i]) + } + return r.Set(acc), nil +} + +// MapToCurve given a byte slice returns a valid G2 point. +// This mapping function implements the Simplified Shallue-van de Woestijne-Ulas method. +// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-6.6.2 +// Input byte slice should be a valid field element, otherwise an error is returned. +func (g *G2) MapToCurve(in []byte) (*PointG2, error) { + fp2 := g.f + u, err := fp2.fromBytes(in) + if err != nil { + return nil, err + } + x, y := swuMapG2(fp2, u) + isogenyMapG2(fp2, x, y) + z := new(fe2).one() + q := &PointG2{*x, *y, *z} + g.ClearCofactor(q) + return g.Affine(q), nil +} diff --git a/common/crypto/bls12381/gt.go b/common/crypto/bls12381/gt.go new file mode 100644 index 0000000..1cc22ce --- /dev/null +++ b/common/crypto/bls12381/gt.go @@ -0,0 +1,122 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//nolint:stylecheck +package bls12381 + +import ( + "errors" + "math/big" +) + +// E is type for target group element +type E = fe12 + +// GT is type for target multiplicative group GT. +type GT struct { + fp12 *fp12 +} + +func (e *E) Set(e2 *E) *E { + return e.set(e2) +} + +// One sets a new target group element to one +func (e *E) One() *E { + e = new(fe12).one() + return e +} + +// IsOne returns true if given element equals to one +func (e *E) IsOne() bool { + return e.isOne() +} + +// Equal returns true if given two element is equal, otherwise returns false +func (g *E) Equal(g2 *E) bool { + return g.equal(g2) +} + +// NewGT constructs new target group instance. +func NewGT() *GT { + fp12 := newFp12(nil) + return >{fp12} +} + +// Q returns group order in big.Int. +func (g *GT) Q() *big.Int { + return new(big.Int).Set(q) +} + +// FromBytes expects 576 byte input and returns target group element +// FromBytes returns error if given element is not on correct subgroup. +func (g *GT) FromBytes(in []byte) (*E, error) { + e, err := g.fp12.fromBytes(in) + if err != nil { + return nil, err + } + if !g.IsValid(e) { + return e, errors.New("invalid element") + } + return e, nil +} + +// ToBytes serializes target group element. +func (g *GT) ToBytes(e *E) []byte { + return g.fp12.toBytes(e) +} + +// IsValid checks whether given target group element is in correct subgroup. +func (g *GT) IsValid(e *E) bool { + r := g.New() + g.fp12.exp(r, e, q) + return r.isOne() +} + +// New initializes a new target group element which is equal to one +func (g *GT) New() *E { + return new(E).One() +} + +// Add adds two field element `a` and `b` and assigns the result to the element in first argument. +func (g *GT) Add(c, a, b *E) { + g.fp12.add(c, a, b) +} + +// Sub subtracts two field element `a` and `b`, and assigns the result to the element in first argument. +func (g *GT) Sub(c, a, b *E) { + g.fp12.sub(c, a, b) +} + +// Mul multiplies two field element `a` and `b` and assigns the result to the element in first argument. +func (g *GT) Mul(c, a, b *E) { + g.fp12.mul(c, a, b) +} + +// Square squares an element `a` and assigns the result to the element in first argument. +func (g *GT) Square(c, a *E) { + g.fp12.cyclotomicSquare(c, a) +} + +// Exp exponents an element `a` by a scalar `s` and assigns the result to the element in first argument. +func (g *GT) Exp(c, a *E, s *big.Int) { + g.fp12.cyclotomicExp(c, a, s) +} + +// Inverse inverses an element `a` and assigns the result to the element in first argument. +func (g *GT) Inverse(c, a *E) { + g.fp12.inverse(c, a) +} diff --git a/common/crypto/bls12381/isogeny.go b/common/crypto/bls12381/isogeny.go new file mode 100644 index 0000000..f3893cb --- /dev/null +++ b/common/crypto/bls12381/isogeny.go @@ -0,0 +1,228 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//nolint:gofmt +package bls12381 + +// isogenyMapG1 applies 11-isogeny map for BLS12-381 G1 defined at draft-irtf-cfrg-hash-to-curve-06. +func isogenyMapG1(x, y *fe) { + // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#appendix-C.2 + params := isogenyConstansG1 + degree := 15 + xNum, xDen, yNum, yDen := new(fe), new(fe), new(fe), new(fe) + xNum.set(params[0][degree]) + xDen.set(params[1][degree]) + yNum.set(params[2][degree]) + yDen.set(params[3][degree]) + for i := degree - 1; i >= 0; i-- { + mul(xNum, xNum, x) + mul(xDen, xDen, x) + mul(yNum, yNum, x) + mul(yDen, yDen, x) + add(xNum, xNum, params[0][i]) + add(xDen, xDen, params[1][i]) + add(yNum, yNum, params[2][i]) + add(yDen, yDen, params[3][i]) + } + inverse(xDen, xDen) + inverse(yDen, yDen) + mul(xNum, xNum, xDen) + mul(yNum, yNum, yDen) + mul(yNum, yNum, y) + x.set(xNum) + y.set(yNum) +} + +// isogenyMapG2 applies 11-isogeny map for BLS12-381 G1 defined at draft-irtf-cfrg-hash-to-curve-06. +func isogenyMapG2(e *fp2, x, y *fe2) { + if e == nil { + e = newFp2() + } + // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#appendix-C.2 + params := isogenyConstantsG2 + degree := 3 + xNum := new(fe2).set(params[0][degree]) + xDen := new(fe2).set(params[1][degree]) + yNum := new(fe2).set(params[2][degree]) + yDen := new(fe2).set(params[3][degree]) + for i := degree - 1; i >= 0; i-- { + e.mul(xNum, xNum, x) + e.mul(xDen, xDen, x) + e.mul(yNum, yNum, x) + e.mul(yDen, yDen, x) + e.add(xNum, xNum, params[0][i]) + e.add(xDen, xDen, params[1][i]) + e.add(yNum, yNum, params[2][i]) + e.add(yDen, yDen, params[3][i]) + } + e.inverse(xDen, xDen) + e.inverse(yDen, yDen) + e.mul(xNum, xNum, xDen) + e.mul(yNum, yNum, yDen) + e.mul(yNum, yNum, y) + x.set(xNum) + y.set(yNum) +} + +var isogenyConstansG1 = [4][16]*fe{ + { + {0x4d18b6f3af00131c, 0x19fa219793fee28c, 0x3f2885f1467f19ae, 0x23dcea34f2ffb304, 0xd15b58d2ffc00054, 0x0913be200a20bef4}, + {0x898985385cdbbd8b, 0x3c79e43cc7d966aa, 0x1597e193f4cd233a, 0x8637ef1e4d6623ad, 0x11b22deed20d827b, 0x07097bc5998784ad}, + {0xa542583a480b664b, 0xfc7169c026e568c6, 0x5ba2ef314ed8b5a6, 0x5b5491c05102f0e7, 0xdf6e99707d2a0079, 0x0784151ed7605524}, + {0x494e212870f72741, 0xab9be52fbda43021, 0x26f5577994e34c3d, 0x049dfee82aefbd60, 0x65dadd7828505289, 0x0e93d431ea011aeb}, + {0x90ee774bd6a74d45, 0x7ada1c8a41bfb185, 0x0f1a8953b325f464, 0x104c24211be4805c, 0x169139d319ea7a8f, 0x09f20ead8e532bf6}, + {0x6ddd93e2f43626b7, 0xa5482c9aa1ccd7bd, 0x143245631883f4bd, 0x2e0a94ccf77ec0db, 0xb0282d480e56489f, 0x18f4bfcbb4368929}, + {0x23c5f0c953402dfd, 0x7a43ff6958ce4fe9, 0x2c390d3d2da5df63, 0xd0df5c98e1f9d70f, 0xffd89869a572b297, 0x1277ffc72f25e8fe}, + {0x79f4f0490f06a8a6, 0x85f894a88030fd81, 0x12da3054b18b6410, 0xe2a57f6505880d65, 0xbba074f260e400f1, 0x08b76279f621d028}, + {0xe67245ba78d5b00b, 0x8456ba9a1f186475, 0x7888bff6e6b33bb4, 0xe21585b9a30f86cb, 0x05a69cdcef55feee, 0x09e699dd9adfa5ac}, + {0x0de5c357bff57107, 0x0a0db4ae6b1a10b2, 0xe256bb67b3b3cd8d, 0x8ad456574e9db24f, 0x0443915f50fd4179, 0x098c4bf7de8b6375}, + {0xe6b0617e7dd929c7, 0xfe6e37d442537375, 0x1dafdeda137a489e, 0xe4efd1ad3f767ceb, 0x4a51d8667f0fe1cf, 0x054fdf4bbf1d821c}, + {0x72db2a50658d767b, 0x8abf91faa257b3d5, 0xe969d6833764ab47, 0x464170142a1009eb, 0xb14f01aadb30be2f, 0x18ae6a856f40715d}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + }, + { + {0xb962a077fdb0f945, 0xa6a9740fefda13a0, 0xc14d568c3ed6c544, 0xb43fc37b908b133e, 0x9c0b3ac929599016, 0x0165aa6c93ad115f}, + {0x23279a3ba506c1d9, 0x92cfca0a9465176a, 0x3b294ab13755f0ff, 0x116dda1c5070ae93, 0xed4530924cec2045, 0x083383d6ed81f1ce}, + {0x9885c2a6449fecfc, 0x4a2b54ccd37733f0, 0x17da9ffd8738c142, 0xa0fba72732b3fafd, 0xff364f36e54b6812, 0x0f29c13c660523e2}, + {0xe349cc118278f041, 0xd487228f2f3204fb, 0xc9d325849ade5150, 0x43a92bd69c15c2df, 0x1c2c7844bc417be4, 0x12025184f407440c}, + {0x587f65ae6acb057b, 0x1444ef325140201f, 0xfbf995e71270da49, 0xccda066072436a42, 0x7408904f0f186bb2, 0x13b93c63edf6c015}, + {0xfb918622cd141920, 0x4a4c64423ecaddb4, 0x0beb232927f7fb26, 0x30f94df6f83a3dc2, 0xaeedd424d780f388, 0x06cc402dd594bbeb}, + {0xd41f761151b23f8f, 0x32a92465435719b3, 0x64f436e888c62cb9, 0xdf70a9a1f757c6e4, 0x6933a38d5b594c81, 0x0c6f7f7237b46606}, + {0x693c08747876c8f7, 0x22c9850bf9cf80f0, 0x8e9071dab950c124, 0x89bc62d61c7baf23, 0xbc6be2d8dad57c23, 0x17916987aa14a122}, + {0x1be3ff439c1316fd, 0x9965243a7571dfa7, 0xc7f7f62962f5cd81, 0x32c6aa9af394361c, 0xbbc2ee18e1c227f4, 0x0c102cbac531bb34}, + {0x997614c97bacbf07, 0x61f86372b99192c0, 0x5b8c95fc14353fc3, 0xca2b066c2a87492f, 0x16178f5bbf698711, 0x12a6dcd7f0f4e0e8}, + {0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + }, + { + {0x2b567ff3e2837267, 0x1d4d9e57b958a767, 0xce028fea04bd7373, 0xcc31a30a0b6cd3df, 0x7d7b18a682692693, 0x0d300744d42a0310}, + {0x99c2555fa542493f, 0xfe7f53cc4874f878, 0x5df0608b8f97608a, 0x14e03832052b49c8, 0x706326a6957dd5a4, 0x0a8dadd9c2414555}, + {0x13d942922a5cf63a, 0x357e33e36e261e7d, 0xcf05a27c8456088d, 0x0000bd1de7ba50f0, 0x83d0c7532f8c1fde, 0x13f70bf38bbf2905}, + {0x5c57fd95bfafbdbb, 0x28a359a65e541707, 0x3983ceb4f6360b6d, 0xafe19ff6f97e6d53, 0xb3468f4550192bf7, 0x0bb6cde49d8ba257}, + {0x590b62c7ff8a513f, 0x314b4ce372cacefd, 0x6bef32ce94b8a800, 0x6ddf84a095713d5f, 0x64eace4cb0982191, 0x0386213c651b888d}, + {0xa5310a31111bbcdd, 0xa14ac0f5da148982, 0xf9ad9cc95423d2e9, 0xaa6ec095283ee4a7, 0xcf5b1f022e1c9107, 0x01fddf5aed881793}, + {0x65a572b0d7a7d950, 0xe25c2d8183473a19, 0xc2fcebe7cb877dbd, 0x05b2d36c769a89b0, 0xba12961be86e9efb, 0x07eb1b29c1dfde1f}, + {0x93e09572f7c4cd24, 0x364e929076795091, 0x8569467e68af51b5, 0xa47da89439f5340f, 0xf4fa918082e44d64, 0x0ad52ba3e6695a79}, + {0x911429844e0d5f54, 0xd03f51a3516bb233, 0x3d587e5640536e66, 0xfa86d2a3a9a73482, 0xa90ed5adf1ed5537, 0x149c9c326a5e7393}, + {0x462bbeb03c12921a, 0xdc9af5fa0a274a17, 0x9a558ebde836ebed, 0x649ef8f11a4fae46, 0x8100e1652b3cdc62, 0x1862bd62c291dacb}, + {0x05c9b8ca89f12c26, 0x0194160fa9b9ac4f, 0x6a643d5a6879fa2c, 0x14665bdd8846e19d, 0xbb1d0d53af3ff6bf, 0x12c7e1c3b28962e5}, + {0xb55ebf900b8a3e17, 0xfedc77ec1a9201c4, 0x1f07db10ea1a4df4, 0x0dfbd15dc41a594d, 0x389547f2334a5391, 0x02419f98165871a4}, + {0xb416af000745fc20, 0x8e563e9d1ea6d0f5, 0x7c763e17763a0652, 0x01458ef0159ebbef, 0x8346fe421f96bb13, 0x0d2d7b829ce324d2}, + {0x93096bb538d64615, 0x6f2a2619951d823a, 0x8f66b3ea59514fa4, 0xf563e63704f7092f, 0x724b136c4cf2d9fa, 0x046959cfcfd0bf49}, + {0xea748d4b6e405346, 0x91e9079c2c02d58f, 0x41064965946d9b59, 0xa06731f1d2bbe1ee, 0x07f897e267a33f1b, 0x1017290919210e5f}, + {0x872aa6c17d985097, 0xeecc53161264562a, 0x07afe37afff55002, 0x54759078e5be6838, 0xc4b92d15db8acca8, 0x106d87d1b51d13b9}, + }, + { + {0xeb6c359d47e52b1c, 0x18ef5f8a10634d60, 0xddfa71a0889d5b7e, 0x723e71dcc5fc1323, 0x52f45700b70d5c69, 0x0a8b981ee47691f1}, + {0x616a3c4f5535b9fb, 0x6f5f037395dbd911, 0xf25f4cc5e35c65da, 0x3e50dffea3c62658, 0x6a33dca523560776, 0x0fadeff77b6bfe3e}, + {0x2be9b66df470059c, 0x24a2c159a3d36742, 0x115dbe7ad10c2a37, 0xb6634a652ee5884d, 0x04fe8bb2b8d81af4, 0x01c2a7a256fe9c41}, + {0xf27bf8ef3b75a386, 0x898b367476c9073f, 0x24482e6b8c2f4e5f, 0xc8e0bbd6fe110806, 0x59b0c17f7631448a, 0x11037cd58b3dbfbd}, + {0x31c7912ea267eec6, 0x1dbf6f1c5fcdb700, 0xd30d4fe3ba86fdb1, 0x3cae528fbee9a2a4, 0xb1cce69b6aa9ad9a, 0x044393bb632d94fb}, + {0xc66ef6efeeb5c7e8, 0x9824c289dd72bb55, 0x71b1a4d2f119981d, 0x104fc1aafb0919cc, 0x0e49df01d942a628, 0x096c3a09773272d4}, + {0x9abc11eb5fadeff4, 0x32dca50a885728f0, 0xfb1fa3721569734c, 0xc4b76271ea6506b3, 0xd466a75599ce728e, 0x0c81d4645f4cb6ed}, + {0x4199f10e5b8be45b, 0xda64e495b1e87930, 0xcb353efe9b33e4ff, 0x9e9efb24aa6424c6, 0xf08d33680a237465, 0x0d3378023e4c7406}, + {0x7eb4ae92ec74d3a5, 0xc341b4aa9fac3497, 0x5be603899e907687, 0x03bfd9cca75cbdeb, 0x564c2935a96bfa93, 0x0ef3c33371e2fdb5}, + {0x7ee91fd449f6ac2e, 0xe5d5bd5cb9357a30, 0x773a8ca5196b1380, 0xd0fda172174ed023, 0x6cb95e0fa776aead, 0x0d22d5a40cec7cff}, + {0xf727e09285fd8519, 0xdc9d55a83017897b, 0x7549d8bd057894ae, 0x178419613d90d8f8, 0xfce95ebdeb5b490a, 0x0467ffaef23fc49e}, + {0xc1769e6a7c385f1b, 0x79bc930deac01c03, 0x5461c75a23ede3b5, 0x6e20829e5c230c45, 0x828e0f1e772a53cd, 0x116aefa749127bff}, + {0x101c10bf2744c10a, 0xbbf18d053a6a3154, 0xa0ecf39ef026f602, 0xfc009d4996dc5153, 0xb9000209d5bd08d3, 0x189e5fe4470cd73c}, + {0x7ebd546ca1575ed2, 0xe47d5a981d081b55, 0x57b2b625b6d4ca21, 0xb0a1ba04228520cc, 0x98738983c2107ff3, 0x13dddbc4799d81d6}, + {0x09319f2e39834935, 0x039e952cbdb05c21, 0x55ba77a9a2f76493, 0xfd04e3dfc6086467, 0xfb95832e7d78742e, 0x0ef9c24eccaf5e0e}, + {0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + }, +} + +var isogenyConstantsG2 = [4][4]*fe2{ + { + { + fe{0x47f671c71ce05e62, 0x06dd57071206393e, 0x7c80cd2af3fd71a2, 0x048103ea9e6cd062, 0xc54516acc8d037f6, 0x13808f550920ea41}, + fe{0x47f671c71ce05e62, 0x06dd57071206393e, 0x7c80cd2af3fd71a2, 0x048103ea9e6cd062, 0xc54516acc8d037f6, 0x13808f550920ea41}, + }, + { + fe{0, 0, 0, 0, 0, 0}, + fe{0x5fe55555554c71d0, 0x873fffdd236aaaa3, 0x6a6b4619b26ef918, 0x21c2888408874945, 0x2836cda7028cabc5, 0x0ac73310a7fd5abd}, + }, + { + fe{0x0a0c5555555971c3, 0xdb0c00101f9eaaae, 0xb1fb2f941d797997, 0xd3960742ef416e1c, 0xb70040e2c20556f4, 0x149d7861e581393b}, + fe{0xaff2aaaaaaa638e8, 0x439fffee91b55551, 0xb535a30cd9377c8c, 0x90e144420443a4a2, 0x941b66d3814655e2, 0x0563998853fead5e}, + }, + { + fe{0x40aac71c71c725ed, 0x190955557a84e38e, 0xd817050a8f41abc3, 0xd86485d4c87f6fb1, 0x696eb479f885d059, 0x198e1a74328002d2}, + fe{0, 0, 0, 0, 0, 0}, + }, + }, + { + { + fe{0, 0, 0, 0, 0, 0}, + fe{0x1f3affffff13ab97, 0xf25bfc611da3ff3e, 0xca3757cb3819b208, 0x3e6427366f8cec18, 0x03977bc86095b089, 0x04f69db13f39a952}, + }, + { + fe{0x447600000027552e, 0xdcb8009a43480020, 0x6f7ee9ce4a6e8b59, 0xb10330b7c0a95bc6, 0x6140b1fcfb1e54b7, 0x0381be097f0bb4e1}, + fe{0x7588ffffffd8557d, 0x41f3ff646e0bffdf, 0xf7b1e8d2ac426aca, 0xb3741acd32dbb6f8, 0xe9daf5b9482d581f, 0x167f53e0ba7431b8}, + }, + { + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0, 0, 0, 0, 0, 0}, + }, + { + fe{0, 0, 0, 0, 0, 0}, + fe{0, 0, 0, 0, 0, 0}, + }, + }, + { + { + fe{0x96d8f684bdfc77be, 0xb530e4f43b66d0e2, 0x184a88ff379652fd, 0x57cb23ecfae804e1, 0x0fd2e39eada3eba9, 0x08c8055e31c5d5c3}, + fe{0x96d8f684bdfc77be, 0xb530e4f43b66d0e2, 0x184a88ff379652fd, 0x57cb23ecfae804e1, 0x0fd2e39eada3eba9, 0x08c8055e31c5d5c3}, + }, + { + fe{0, 0, 0, 0, 0, 0}, + fe{0xbf0a71c71c91b406, 0x4d6d55d28b7638fd, 0x9d82f98e5f205aee, 0xa27aa27b1d1a18d5, 0x02c3b2b2d2938e86, 0x0c7d13420b09807f}, + }, + { + fe{0xd7f9555555531c74, 0x21cffff748daaaa8, 0x5a9ad1866c9bbe46, 0x4870a2210221d251, 0x4a0db369c0a32af1, 0x02b1ccc429ff56af}, + fe{0xe205aaaaaaac8e37, 0xfcdc000768795556, 0x0c96011a8a1537dd, 0x1c06a963f163406e, 0x010df44c82a881e6, 0x174f45260f808feb}, + }, + { + fe{0xa470bda12f67f35c, 0xc0fe38e23327b425, 0xc9d3d0f2c6f0678d, 0x1c55c9935b5a982e, 0x27f6c0e2f0746764, 0x117c5e6e28aa9054}, + fe{0, 0, 0, 0, 0, 0}, + }, + }, + { + { + fe{0x0162fffffa765adf, 0x8f7bea480083fb75, 0x561b3c2259e93611, 0x11e19fc1a9c875d5, 0xca713efc00367660, 0x03c6a03d41da1151}, + fe{0x0162fffffa765adf, 0x8f7bea480083fb75, 0x561b3c2259e93611, 0x11e19fc1a9c875d5, 0xca713efc00367660, 0x03c6a03d41da1151}, + }, + { + fe{0, 0, 0, 0, 0, 0}, + fe{0x5db0fffffd3b02c5, 0xd713f52358ebfdba, 0x5ea60761a84d161a, 0xbb2c75a34ea6c44a, 0x0ac6735921c1119b, 0x0ee3d913bdacfbf6}, + }, + { + fe{0x66b10000003affc5, 0xcb1400e764ec0030, 0xa73e5eb56fa5d106, 0x8984c913a0fe09a9, 0x11e10afb78ad7f13, 0x05429d0e3e918f52}, + fe{0x534dffffffc4aae6, 0x5397ff174c67ffcf, 0xbff273eb870b251d, 0xdaf2827152870915, 0x393a9cbaca9e2dc3, 0x14be74dbfaee5748}, + }, + { + fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}, + fe{0, 0, 0, 0, 0, 0}, + }, + }, +} diff --git a/common/crypto/bls12381/pairing.go b/common/crypto/bls12381/pairing.go new file mode 100644 index 0000000..15d3fb0 --- /dev/null +++ b/common/crypto/bls12381/pairing.go @@ -0,0 +1,282 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +type pair struct { + g1 *PointG1 + g2 *PointG2 +} + +func newPair(g1 *PointG1, g2 *PointG2) pair { + return pair{g1, g2} +} + +// Engine is BLS12-381 elliptic curve pairing engine +type Engine struct { + G1 *G1 + G2 *G2 + fp12 *fp12 + fp2 *fp2 + pairingEngineTemp + pairs []pair +} + +// NewPairingEngine creates new pairing engine instance. +func NewPairingEngine() *Engine { + fp2 := newFp2() + fp6 := newFp6(fp2) + fp12 := newFp12(fp6) + g1 := NewG1() + g2 := newG2(fp2) + return &Engine{ + fp2: fp2, + fp12: fp12, + G1: g1, + G2: g2, + pairingEngineTemp: newEngineTemp(), + } +} + +type pairingEngineTemp struct { + t2 [10]*fe2 + t12 [9]fe12 +} + +func newEngineTemp() pairingEngineTemp { + t2 := [10]*fe2{} + for i := 0; i < 10; i++ { + t2[i] = &fe2{} + } + t12 := [9]fe12{} + return pairingEngineTemp{t2, t12} +} + +// AddPair adds a g1, g2 point pair to pairing engine +func (e *Engine) AddPair(g1 *PointG1, g2 *PointG2) *Engine { + p := newPair(g1, g2) + if !e.isZero(p) { + e.affine(p) + e.pairs = append(e.pairs, p) + } + return e +} + +// AddPairInv adds a G1, G2 point pair to pairing engine. G1 point is negated. +func (e *Engine) AddPairInv(g1 *PointG1, g2 *PointG2) *Engine { + e.G1.Neg(g1, g1) + e.AddPair(g1, g2) + return e +} + +// Reset deletes added pairs. +func (e *Engine) Reset() *Engine { + e.pairs = []pair{} + return e +} + +func (e *Engine) isZero(p pair) bool { + return e.G1.IsZero(p.g1) || e.G2.IsZero(p.g2) +} + +func (e *Engine) affine(p pair) { + e.G1.Affine(p.g1) + e.G2.Affine(p.g2) +} + +func (e *Engine) doublingStep(coeff *[3]fe2, r *PointG2) { + // Adaptation of Formula 3 in https://eprint.iacr.org/2010/526.pdf + fp2 := e.fp2 + t := e.t2 + fp2.mul(t[0], &r[0], &r[1]) + fp2.mulByFq(t[0], t[0], twoInv) + fp2.square(t[1], &r[1]) + fp2.square(t[2], &r[2]) + fp2.double(t[7], t[2]) + fp2.add(t[7], t[7], t[2]) + fp2.mulByB(t[3], t[7]) + fp2.double(t[4], t[3]) + fp2.add(t[4], t[4], t[3]) + fp2.add(t[5], t[1], t[4]) + fp2.mulByFq(t[5], t[5], twoInv) + fp2.add(t[6], &r[1], &r[2]) + fp2.square(t[6], t[6]) + fp2.add(t[7], t[2], t[1]) + fp2.sub(t[6], t[6], t[7]) + fp2.sub(&coeff[0], t[3], t[1]) + fp2.square(t[7], &r[0]) + fp2.sub(t[4], t[1], t[4]) + fp2.mul(&r[0], t[4], t[0]) + fp2.square(t[2], t[3]) + fp2.double(t[3], t[2]) + fp2.add(t[3], t[3], t[2]) + fp2.square(t[5], t[5]) + fp2.sub(&r[1], t[5], t[3]) + fp2.mul(&r[2], t[1], t[6]) + fp2.double(t[0], t[7]) + fp2.add(&coeff[1], t[0], t[7]) + fp2.neg(&coeff[2], t[6]) +} + +func (e *Engine) additionStep(coeff *[3]fe2, r, q *PointG2) { + // Algorithm 12 in https://eprint.iacr.org/2010/526.pdf + fp2 := e.fp2 + t := e.t2 + fp2.mul(t[0], &q[1], &r[2]) + fp2.neg(t[0], t[0]) + fp2.add(t[0], t[0], &r[1]) + fp2.mul(t[1], &q[0], &r[2]) + fp2.neg(t[1], t[1]) + fp2.add(t[1], t[1], &r[0]) + fp2.square(t[2], t[0]) + fp2.square(t[3], t[1]) + fp2.mul(t[4], t[1], t[3]) + fp2.mul(t[2], &r[2], t[2]) + fp2.mul(t[3], &r[0], t[3]) + fp2.double(t[5], t[3]) + fp2.sub(t[5], t[4], t[5]) + fp2.add(t[5], t[5], t[2]) + fp2.mul(&r[0], t[1], t[5]) + fp2.sub(t[2], t[3], t[5]) + fp2.mul(t[2], t[2], t[0]) + fp2.mul(t[3], &r[1], t[4]) + fp2.sub(&r[1], t[2], t[3]) + fp2.mul(&r[2], &r[2], t[4]) + fp2.mul(t[2], t[1], &q[1]) + fp2.mul(t[3], t[0], &q[0]) + fp2.sub(&coeff[0], t[3], t[2]) + fp2.neg(&coeff[1], t[0]) + coeff[2].set(t[1]) +} + +func (e *Engine) preCompute(ellCoeffs *[68][3]fe2, twistPoint *PointG2) { + // Algorithm 5 in https://eprint.iacr.org/2019/077.pdf + if e.G2.IsZero(twistPoint) { + return + } + r := new(PointG2).Set(twistPoint) + j := 0 + for i := x.BitLen() - 2; i >= 0; i-- { + e.doublingStep(&ellCoeffs[j], r) + if x.Bit(i) != 0 { + j++ + ellCoeffs[j] = fe6{} + e.additionStep(&ellCoeffs[j], r, twistPoint) + } + j++ + } +} + +func (e *Engine) millerLoop(f *fe12) { + pairs := e.pairs + ellCoeffs := make([][68][3]fe2, len(pairs)) + for i := 0; i < len(pairs); i++ { + e.preCompute(&ellCoeffs[i], pairs[i].g2) + } + fp12, fp2 := e.fp12, e.fp2 + t := e.t2 + f.one() + j := 0 + for i := 62; /* x.BitLen() - 2 */ i >= 0; i-- { + if i != 62 { + fp12.square(f, f) + } + for i := 0; i <= len(pairs)-1; i++ { //nolint:govet + fp2.mulByFq(t[0], &ellCoeffs[i][j][2], &pairs[i].g1[1]) + fp2.mulByFq(t[1], &ellCoeffs[i][j][1], &pairs[i].g1[0]) + fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0]) + } + if x.Bit(i) != 0 { + j++ + for i := 0; i <= len(pairs)-1; i++ { //nolint:govet + fp2.mulByFq(t[0], &ellCoeffs[i][j][2], &pairs[i].g1[1]) + fp2.mulByFq(t[1], &ellCoeffs[i][j][1], &pairs[i].g1[0]) + fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0]) + } + } + j++ + } + fp12.conjugate(f, f) +} + +func (e *Engine) exp(c, a *fe12) { + fp12 := e.fp12 + fp12.cyclotomicExp(c, a, x) + fp12.conjugate(c, c) +} + +func (e *Engine) finalExp(f *fe12) { + fp12 := e.fp12 + t := e.t12 + // easy part + fp12.frobeniusMap(&t[0], f, 6) + fp12.inverse(&t[1], f) + fp12.mul(&t[2], &t[0], &t[1]) + t[1].set(&t[2]) + fp12.frobeniusMapAssign(&t[2], 2) + fp12.mulAssign(&t[2], &t[1]) + fp12.cyclotomicSquare(&t[1], &t[2]) + fp12.conjugate(&t[1], &t[1]) + // hard part + e.exp(&t[3], &t[2]) + fp12.cyclotomicSquare(&t[4], &t[3]) + fp12.mul(&t[5], &t[1], &t[3]) + e.exp(&t[1], &t[5]) + e.exp(&t[0], &t[1]) + e.exp(&t[6], &t[0]) + fp12.mulAssign(&t[6], &t[4]) + e.exp(&t[4], &t[6]) + fp12.conjugate(&t[5], &t[5]) + fp12.mulAssign(&t[4], &t[5]) + fp12.mulAssign(&t[4], &t[2]) + fp12.conjugate(&t[5], &t[2]) + fp12.mulAssign(&t[1], &t[2]) + fp12.frobeniusMapAssign(&t[1], 3) + fp12.mulAssign(&t[6], &t[5]) + fp12.frobeniusMapAssign(&t[6], 1) + fp12.mulAssign(&t[3], &t[0]) + fp12.frobeniusMapAssign(&t[3], 2) + fp12.mulAssign(&t[3], &t[1]) + fp12.mulAssign(&t[3], &t[6]) + fp12.mul(f, &t[3], &t[4]) +} + +func (e *Engine) calculate() *fe12 { + f := e.fp12.one() + if len(e.pairs) == 0 { + return f + } + e.millerLoop(f) + e.finalExp(f) + return f +} + +// Check computes pairing and checks if result is equal to one +func (e *Engine) Check() bool { + return e.calculate().isOne() +} + +// Result computes pairing and returns target group element as result. +func (e *Engine) Result() *E { + r := e.calculate() + e.Reset() + return r +} + +// GT returns target group instance. +func (e *Engine) GT() *GT { + return NewGT() +} diff --git a/common/crypto/bls12381/swu.go b/common/crypto/bls12381/swu.go new file mode 100644 index 0000000..0f81aa6 --- /dev/null +++ b/common/crypto/bls12381/swu.go @@ -0,0 +1,158 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +// swuMapG1 is implementation of Simplified Shallue-van de Woestijne-Ulas Method +// follows the implmentation at draft-irtf-cfrg-hash-to-curve-06. +func swuMapG1(u *fe) (*fe, *fe) { + var params = swuParamsForG1 + var tv [4]*fe + for i := 0; i < 4; i++ { + tv[i] = new(fe) + } + square(tv[0], u) + mul(tv[0], tv[0], params.z) + square(tv[1], tv[0]) + x1 := new(fe) + add(x1, tv[0], tv[1]) + inverse(x1, x1) + e1 := x1.isZero() + one := new(fe).one() + add(x1, x1, one) + if e1 { + x1.set(params.zInv) + } + mul(x1, x1, params.minusBOverA) + gx1 := new(fe) + square(gx1, x1) + add(gx1, gx1, params.a) + mul(gx1, gx1, x1) + add(gx1, gx1, params.b) + x2 := new(fe) + mul(x2, tv[0], x1) + mul(tv[1], tv[0], tv[1]) + gx2 := new(fe) + mul(gx2, gx1, tv[1]) + e2 := !isQuadraticNonResidue(gx1) + x, y2 := new(fe), new(fe) + if e2 { + x.set(x1) + y2.set(gx1) + } else { + x.set(x2) + y2.set(gx2) + } + y := new(fe) + sqrt(y, y2) + if y.sign() != u.sign() { + neg(y, y) + } + return x, y +} + +// swuMapG2 is implementation of Simplified Shallue-van de Woestijne-Ulas Method +// defined at draft-irtf-cfrg-hash-to-curve-06. +func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) { + if e == nil { + e = newFp2() + } + params := swuParamsForG2 + var tv [4]*fe2 + for i := 0; i < 4; i++ { + tv[i] = e.new() + } + e.square(tv[0], u) + e.mul(tv[0], tv[0], params.z) + e.square(tv[1], tv[0]) + x1 := e.new() + e.add(x1, tv[0], tv[1]) + e.inverse(x1, x1) + e1 := x1.isZero() + e.add(x1, x1, e.one()) + if e1 { + x1.set(params.zInv) + } + e.mul(x1, x1, params.minusBOverA) + gx1 := e.new() + e.square(gx1, x1) + e.add(gx1, gx1, params.a) + e.mul(gx1, gx1, x1) + e.add(gx1, gx1, params.b) + x2 := e.new() + e.mul(x2, tv[0], x1) + e.mul(tv[1], tv[0], tv[1]) + gx2 := e.new() + e.mul(gx2, gx1, tv[1]) + e2 := !e.isQuadraticNonResidue(gx1) + x, y2 := e.new(), e.new() + if e2 { + x.set(x1) + y2.set(gx1) + } else { + x.set(x2) + y2.set(gx2) + } + y := e.new() + e.sqrt(y, y2) + if y.sign() != u.sign() { + e.neg(y, y) + } + return x, y +} + +var swuParamsForG1 = struct { + z *fe + zInv *fe + a *fe + b *fe + minusBOverA *fe +}{ + a: &fe{0x2f65aa0e9af5aa51, 0x86464c2d1e8416c3, 0xb85ce591b7bd31e2, 0x27e11c91b5f24e7c, 0x28376eda6bfc1835, 0x155455c3e5071d85}, + b: &fe{0xfb996971fe22a1e0, 0x9aa93eb35b742d6f, 0x8c476013de99c5c4, 0x873e27c3a221e571, 0xca72b5e45a52d888, 0x06824061418a386b}, + z: &fe{0x886c00000023ffdc, 0x0f70008d3090001d, 0x77672417ed5828c3, 0x9dac23e943dc1740, 0x50553f1b9c131521, 0x078c712fbe0ab6e8}, + zInv: &fe{0x0e8a2e8ba2e83e10, 0x5b28ba2ca4d745d1, 0x678cd5473847377a, 0x4c506dd8a8076116, 0x9bcb227d79284139, 0x0e8d3154b0ba099a}, + minusBOverA: &fe{0x052583c93555a7fe, 0x3b40d72430f93c82, 0x1b75faa0105ec983, 0x2527e7dc63851767, 0x99fffd1f34fc181d, 0x097cab54770ca0d3}, +} + +var swuParamsForG2 = struct { + z *fe2 + zInv *fe2 + a *fe2 + b *fe2 + minusBOverA *fe2 +}{ + a: &fe2{ + fe{0, 0, 0, 0, 0, 0}, + fe{0xe53a000003135242, 0x01080c0fdef80285, 0xe7889edbe340f6bd, 0x0b51375126310601, 0x02d6985717c744ab, 0x1220b4e979ea5467}, + }, + b: &fe2{ + fe{0x22ea00000cf89db2, 0x6ec832df71380aa4, 0x6e1b94403db5a66e, 0x75bf3c53a79473ba, 0x3dd3a569412c0a34, 0x125cdb5e74dc4fd1}, + fe{0x22ea00000cf89db2, 0x6ec832df71380aa4, 0x6e1b94403db5a66e, 0x75bf3c53a79473ba, 0x3dd3a569412c0a34, 0x125cdb5e74dc4fd1}, + }, + z: &fe2{ + fe{0x87ebfffffff9555c, 0x656fffe5da8ffffa, 0x0fd0749345d33ad2, 0xd951e663066576f4, 0xde291a3d41e980d3, 0x0815664c7dfe040d}, + fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206}, + }, + zInv: &fe2{ + fe{0xacd0000000011110, 0x9dd9999dc88ccccd, 0xb5ca2ac9b76352bf, 0xf1b574bcf4bc90ce, 0x42dab41f28a77081, 0x132fc6ac14cd1e12}, + fe{0xe396ffffffff2223, 0x4fbf332fcd0d9998, 0x0c4bbd3c1aff4cc4, 0x6b9c91267926ca58, 0x29ae4da6aef7f496, 0x10692e942f195791}, + }, + minusBOverA: &fe2{ + fe{0x903c555555474fb3, 0x5f98cc95ce451105, 0x9f8e582eefe0fade, 0xc68946b6aebbd062, 0x467a4ad10ee6de53, 0x0e7146f483e23a05}, + fe{0x29c2aaaaaab85af8, 0xbf133368e30eeefa, 0xc7a27a7206cffb45, 0x9dee04ce44c9425c, 0x04a15ce53464ce83, 0x0b8fcaf5b59dac95}, + }, +} diff --git a/common/crypto/bls12381/utils.go b/common/crypto/bls12381/utils.go new file mode 100644 index 0000000..c5b292a --- /dev/null +++ b/common/crypto/bls12381/utils.go @@ -0,0 +1,48 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bls12381 + +import ( + "errors" + "github.com/holiman/uint256" + "math/big" +) + +func bigFromHex(hex string) *big.Int { + i256, err := uint256.FromHex(hex) + if nil != err { + return uint256.NewInt(0).ToBig() + } + return i256.ToBig() +} + +// decodeFieldElement expects 64 byte input with zero top 16 bytes, +// returns lower 48 bytes. +func decodeFieldElement(in []byte) ([]byte, error) { + if len(in) != 64 { + return nil, errors.New("invalid field element length") + } + // check top bytes + for i := 0; i < 16; i++ { + if in[i] != byte(0x00) { + return nil, errors.New("invalid field element top bytes") + } + } + out := make([]byte, 48) + copy(out, in[16:]) + return out, nil +} diff --git a/common/crypto/bn256/LICENSE b/common/crypto/bn256/LICENSE new file mode 100644 index 0000000..634e0cb --- /dev/null +++ b/common/crypto/bn256/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2018 Péter Szilágyi. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/crypto/bn256/bn256_fast.go b/common/crypto/bn256/bn256_fast.go new file mode 100644 index 0000000..45e98d7 --- /dev/null +++ b/common/crypto/bn256/bn256_fast.go @@ -0,0 +1,25 @@ +// Copyright 2018 Péter Szilágyi. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +//go:build amd64 || arm64 + +// Package bn256 implements the Optimal Ate pairing over a 256-bit Barreto-Naehrig curve. +package bn256 + +import ( + bn256cf "github.com/astranetworld/ast/common/crypto/bn256/cloudflare" +) + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 = bn256cf.G1 + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 = bn256cf.G2 + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + return bn256cf.PairingCheck(a, b) +} diff --git a/common/crypto/bn256/bn256_slow.go b/common/crypto/bn256/bn256_slow.go new file mode 100644 index 0000000..0da7f6c --- /dev/null +++ b/common/crypto/bn256/bn256_slow.go @@ -0,0 +1,23 @@ +// Copyright 2018 Péter Szilágyi. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +//go:build !amd64 && !arm64 + +// Package bn256 implements the Optimal Ate pairing over a 256-bit Barreto-Naehrig curve. +package bn256 + +import bn256 "github.com/astranetworld/ast/common/crypto/bn256/google" + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 = bn256.G1 + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 = bn256.G2 + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + return bn256.PairingCheck(a, b) +} diff --git a/common/crypto/bn256/cloudflare/LICENSE b/common/crypto/bn256/cloudflare/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/common/crypto/bn256/cloudflare/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/crypto/bn256/cloudflare/bn256.go b/common/crypto/bn256/cloudflare/bn256.go new file mode 100644 index 0000000..4f607af --- /dev/null +++ b/common/crypto/bn256/cloudflare/bn256.go @@ -0,0 +1,495 @@ +// Package bn256 implements a particular bilinear group at the 128-bit security +// level. +// +// Bilinear groups are the basis of many of the new cryptographic protocols that +// have been proposed over the past decade. They consist of a triplet of groups +// (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ +// is a generator of the respective group). That function is called a pairing +// function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) +package bn256 + +import ( + "crypto/rand" + "errors" + "io" + "math/big" +) + +func randomK(r io.Reader) (k *big.Int, err error) { + for { + k, err = rand.Int(r, Order) + if err != nil || k.Sign() > 0 { + return + } + } +} + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 struct { + p *curvePoint +} + +// RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r. +func RandomG1(r io.Reader) (*big.Int, *G1, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G1).ScalarBaseMult(k), nil +} + +func (g *G1) String() string { + return "bn256.G1" + g.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns e. +func (e *G1) ScalarBaseMult(k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(curveGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G1) Add(a, b *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G1) Neg(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G1) Set(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e to a byte slice. +func (e *G1) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*2) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x) + temp.Marshal(ret) + montDecode(temp, &e.p.y) + temp.Marshal(ret[numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G1) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 2*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &curvePoint{} + } else { + e.p.x, e.p.y = gfP{0}, gfP{0} + } + var err error + if err = e.p.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x, &e.p.x) + montEncode(&e.p.y, &e.p.y) + + zero := gfP{0} + if e.p.x == zero && e.p.y == zero { + // This is the point at infinity. + e.p.y = *newGFp(1) + e.p.z = gfP{0} + e.p.t = gfP{0} + } else { + e.p.z = *newGFp(1) + e.p.t = *newGFp(1) + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[2*numBytes:], nil +} + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 struct { + p *twistPoint +} + +// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r. +func RandomG2(r io.Reader) (*big.Int, *G2, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G2).ScalarBaseMult(k), nil +} + +func (e *G2) String() string { + return "bn256.G2" + e.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns out. +func (e *G2) ScalarBaseMult(k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(twistGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G2) Add(a, b *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G2) Neg(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G2) Set(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e into a byte slice. +func (e *G2) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &twistPoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*4) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.y.y) + temp.Marshal(ret[3*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G2) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 4*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &twistPoint{} + } + var err error + if err = e.p.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x.x, &e.p.x.x) + montEncode(&e.p.x.y, &e.p.x.y) + montEncode(&e.p.y.x, &e.p.y.x) + montEncode(&e.p.y.y, &e.p.y.y) + + if e.p.x.IsZero() && e.p.y.IsZero() { + // This is the point at infinity. + e.p.y.SetOne() + e.p.z.SetZero() + e.p.t.SetZero() + } else { + e.p.z.SetOne() + e.p.t.SetOne() + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[4*numBytes:], nil +} + +// GT is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type GT struct { + p *gfP12 +} + +// Pair calculates an Optimal Ate pairing. +func Pair(g1 *G1, g2 *G2) *GT { + return >{optimalAte(g2.p, g1.p)} +} + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + acc := new(gfP12) + acc.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].p.IsInfinity() || b[i].p.IsInfinity() { + continue + } + acc.Mul(acc, miller(b[i].p, a[i].p)) + } + return finalExponentiation(acc).IsOne() +} + +// Miller applies Miller's algorithm, which is a bilinear function from the +// source groups to F_p^12. Miller(g1, g2).Finalize() is equivalent to Pair(g1, +// g2). +func Miller(g1 *G1, g2 *G2) *GT { + return >{miller(g2.p, g1.p)} +} + +func (g *GT) String() string { + return "bn256.GT" + g.p.String() +} + +// ScalarMult sets e to a*k and then returns e. +func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Exp(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *GT) Add(a, b *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Mul(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *GT) Neg(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Conjugate(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *GT) Set(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Set(a.p) + return e +} + +// Finalize is a linear function from F_p^12 to GT. +func (e *GT) Finalize() *GT { + ret := finalExponentiation(e.p) + e.p.Set(ret) + return e +} + +// Marshal converts e into a byte slice. +func (e *GT) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &gfP12{} + e.p.SetOne() + } + + ret := make([]byte, numBytes*12) + temp := &gfP{} + + montDecode(temp, &e.p.x.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.x.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.x.y.y) + temp.Marshal(ret[3*numBytes:]) + montDecode(temp, &e.p.x.z.x) + temp.Marshal(ret[4*numBytes:]) + montDecode(temp, &e.p.x.z.y) + temp.Marshal(ret[5*numBytes:]) + montDecode(temp, &e.p.y.x.x) + temp.Marshal(ret[6*numBytes:]) + montDecode(temp, &e.p.y.x.y) + temp.Marshal(ret[7*numBytes:]) + montDecode(temp, &e.p.y.y.x) + temp.Marshal(ret[8*numBytes:]) + montDecode(temp, &e.p.y.y.y) + temp.Marshal(ret[9*numBytes:]) + montDecode(temp, &e.p.y.z.x) + temp.Marshal(ret[10*numBytes:]) + montDecode(temp, &e.p.y.z.y) + temp.Marshal(ret[11*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *GT) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if len(m) < 12*numBytes { + return nil, errors.New("bn256: not enough data") + } + + if e.p == nil { + e.p = &gfP12{} + } + + var err error + if err = e.p.x.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.x.Unmarshal(m[4*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.y.Unmarshal(m[5*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.x.Unmarshal(m[6*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.y.Unmarshal(m[7*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.x.Unmarshal(m[8*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.y.Unmarshal(m[9*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.x.Unmarshal(m[10*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.y.Unmarshal(m[11*numBytes:]); err != nil { + return nil, err + } + montEncode(&e.p.x.x.x, &e.p.x.x.x) + montEncode(&e.p.x.x.y, &e.p.x.x.y) + montEncode(&e.p.x.y.x, &e.p.x.y.x) + montEncode(&e.p.x.y.y, &e.p.x.y.y) + montEncode(&e.p.x.z.x, &e.p.x.z.x) + montEncode(&e.p.x.z.y, &e.p.x.z.y) + montEncode(&e.p.y.x.x, &e.p.y.x.x) + montEncode(&e.p.y.x.y, &e.p.y.x.y) + montEncode(&e.p.y.y.x, &e.p.y.y.x) + montEncode(&e.p.y.y.y, &e.p.y.y.y) + montEncode(&e.p.y.z.x, &e.p.y.z.x) + montEncode(&e.p.y.z.y, &e.p.y.z.y) + + return m[12*numBytes:], nil +} diff --git a/common/crypto/bn256/cloudflare/bn256_test.go b/common/crypto/bn256/cloudflare/bn256_test.go new file mode 100644 index 0000000..8af2d38 --- /dev/null +++ b/common/crypto/bn256/cloudflare/bn256_test.go @@ -0,0 +1,132 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +import ( + "bytes" + "crypto/rand" + "testing" +) + +func TestG1Marshal(t *testing.T) { + _, Ga, err := RandomG1(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G1) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestG2Marshal(t *testing.T) { + _, Ga, err := RandomG2(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G2) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestBilinearity(t *testing.T) { + for i := 0; i < 2; i++ { + a, p1, _ := RandomG1(rand.Reader) + b, p2, _ := RandomG2(rand.Reader) + e1 := Pair(p1, p2) + + e2 := Pair(&G1{curveGen}, &G2{twistGen}) + e2.ScalarMult(e2, a) + e2.ScalarMult(e2, b) + + if *e1.p != *e2.p { + t.Fatalf("bad pairing result: %s", e1) + } + } +} + +func TestTripartiteDiffieHellman(t *testing.T) { + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + pa, pb, pc := new(G1), new(G1), new(G1) + qa, qb, qc := new(G2), new(G2), new(G2) + + pa.Unmarshal(new(G1).ScalarBaseMult(a).Marshal()) + qa.Unmarshal(new(G2).ScalarBaseMult(a).Marshal()) + pb.Unmarshal(new(G1).ScalarBaseMult(b).Marshal()) + qb.Unmarshal(new(G2).ScalarBaseMult(b).Marshal()) + pc.Unmarshal(new(G1).ScalarBaseMult(c).Marshal()) + qc.Unmarshal(new(G2).ScalarBaseMult(c).Marshal()) + + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + k1Bytes := k1.Marshal() + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + k2Bytes := k2.Marshal() + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + k3Bytes := k3.Marshal() + + if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) { + t.Errorf("keys didn't agree") + } +} + +func BenchmarkG1(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G1).ScalarBaseMult(x) + } +} + +func BenchmarkG2(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G2).ScalarBaseMult(x) + } +} +func BenchmarkPairing(b *testing.B) { + for i := 0; i < b.N; i++ { + Pair(&G1{curveGen}, &G2{twistGen}) + } +} diff --git a/common/crypto/bn256/cloudflare/constants.go b/common/crypto/bn256/cloudflare/constants.go new file mode 100644 index 0000000..f7d2c7c --- /dev/null +++ b/common/crypto/bn256/cloudflare/constants.go @@ -0,0 +1,62 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +func bigFromBase10(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 10) + return n +} + +// u is the BN parameter. +var u = bigFromBase10("4965661367192848881") + +// Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. +var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. +var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") + +// p2 is p, represented as little-endian 64-bit words. +var p2 = [4]uint64{0x3c208c16d87cfd47, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + +// np is the negative inverse of p, mod 2^256. +var np = [4]uint64{0x87d20782e4866389, 0x9ede7d651eca6ac9, 0xd8afcbd01833da80, 0xf57a22b791888c6b} + +// rN1 is R^-1 where R = 2^256 mod p. +var rN1 = &gfP{0xed84884a014afa37, 0xeb2022850278edf8, 0xcf63e9cfb74492d9, 0x2e67157159e5c639} + +// r2 is R^2 where R = 2^256 mod p. +var r2 = &gfP{0xf32cfc5b538afa89, 0xb5e71911d44501fb, 0x47ab1eff0a417ff6, 0x06d89f71cab8351f} + +// r3 is R^3 where R = 2^256 mod p. +var r3 = &gfP{0xb1cd6dafda1530df, 0x62f210e6a7283db6, 0xef7f0b0c0ada0afb, 0x20fd6e902d592544} + +// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. +var xiToPMinus1Over6 = &gfP2{gfP{0xa222ae234c492d72, 0xd00f02a4565de15b, 0xdc2ff3a253dfc926, 0x10a75716b3899551}, gfP{0xaf9ba69633144907, 0xca6b1d7387afb78a, 0x11bded5ef08a2087, 0x02f34d751a1f3a7c}} + +// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. +var xiToPMinus1Over3 = &gfP2{gfP{0x6e849f1ea0aa4757, 0xaa1c7b6d89f89141, 0xb6e713cdfae0ca3a, 0x26694fbb4e82ebc3}, gfP{0xb5773b104563ab30, 0x347f91c8a9aa6454, 0x7a007127242e0991, 0x1956bcd8118214ec}} + +// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+9. +var xiToPMinus1Over2 = &gfP2{gfP{0xa1d77ce45ffe77c7, 0x07affd117826d1db, 0x6d16bd27bb7edc6b, 0x2c87200285defecc}, gfP{0xe4bbdd0c2936b629, 0xbb30f162e133bacb, 0x31a9d1b6f9645366, 0x253570bea500f8dd}} + +// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. +var xiToPSquaredMinus1Over3 = &gfP{0x3350c88e13e80b9c, 0x7dce557cdb5e56b9, 0x6001b4b8b615564a, 0x2682e617020217e0} + +// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). +var xiTo2PSquaredMinus2Over3 = &gfP{0x71930c11d782e155, 0xa6bb947cffbe3323, 0xaa303344d4741444, 0x2c3b3f0d26594943} + +// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). +var xiToPSquaredMinus1Over6 = &gfP{0xca8d800500fa1bf2, 0xf0c5d61468b39769, 0x0e201271ad0d4418, 0x04290f65bad856e6} + +// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. +var xiTo2PMinus2Over3 = &gfP2{gfP{0x5dddfd154bd8c949, 0x62cb29a5a4445b60, 0x37bc870a0c7dd2b9, 0x24830a9d3171f0fd}, gfP{0x7361d77f843abe92, 0xa5bb2bd3273411fb, 0x9c941f314b3e2399, 0x15df9cddbb9fd3ec}} diff --git a/common/crypto/bn256/cloudflare/curve.go b/common/crypto/bn256/cloudflare/curve.go new file mode 100644 index 0000000..253b5a0 --- /dev/null +++ b/common/crypto/bn256/cloudflare/curve.go @@ -0,0 +1,254 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +import ( + "math/big" +) + +// curvePoint implements the elliptic curve y²=x³+3. Points are kept in Jacobian +// form and t=z² when valid. G₁ is the set of points of this curve on GF(p). +type curvePoint struct { + x, y, z, t gfP +} + +var curveB = newGFp(3) + +// curveGen is the generator of G₁. +var curveGen = &curvePoint{ + x: *newGFp(1), + y: *newGFp(2), + z: *newGFp(1), + t: *newGFp(1), +} + +func (c *curvePoint) String() string { + c.MakeAffine() + x, y := &gfP{}, &gfP{} + montDecode(x, &c.x) + montDecode(y, &c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *curvePoint) Set(a *curvePoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *curvePoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP{}, &gfP{} + gfpMul(y2, &c.y, &c.y) + gfpMul(x3, &c.x, &c.x) + gfpMul(x3, x3, &c.x) + gfpAdd(x3, x3, curveB) + + return *y2 == *x3 +} + +func (c *curvePoint) SetInfinity() { + c.x = gfP{0} + c.y = *newGFp(1) + c.z = gfP{0} + c.t = gfP{0} +} + +func (c *curvePoint) IsInfinity() bool { + return c.z == gfP{0} +} + +func (c *curvePoint) Add(a, b *curvePoint) { + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + + // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2] + // by [u1:s1:z1·z2] and [u2:s2:z1·z2] + // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ + z12, z22 := &gfP{}, &gfP{} + gfpMul(z12, &a.z, &a.z) + gfpMul(z22, &b.z, &b.z) + + u1, u2 := &gfP{}, &gfP{} + gfpMul(u1, &a.x, z22) + gfpMul(u2, &b.x, z12) + + t, s1 := &gfP{}, &gfP{} + gfpMul(t, &b.z, z22) + gfpMul(s1, &a.y, t) + + s2 := &gfP{} + gfpMul(t, &a.z, z12) + gfpMul(s2, &b.y, t) + + // Compute x = (2h)²(s²-u1-u2) + // where s = (s2-s1)/(u2-u1) is the slope of the line through + // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below. + // This is also: + // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1) + // = r² - j - 2v + // with the notations below. + h := &gfP{} + gfpSub(h, u2, u1) + xEqual := *h == gfP{0} + + gfpAdd(t, h, h) + // i = 4h² + i := &gfP{} + gfpMul(i, t, t) + // j = 4h³ + j := &gfP{} + gfpMul(j, h, i) + + gfpSub(t, s2, s1) + yEqual := *t == gfP{0} + if xEqual && yEqual { + c.Double(a) + return + } + r := &gfP{} + gfpAdd(r, t, t) + + v := &gfP{} + gfpMul(v, u1, i) + + // t4 = 4(s2-s1)² + t4, t6 := &gfP{}, &gfP{} + gfpMul(t4, r, r) + gfpAdd(t, v, v) + gfpSub(t6, t4, j) + + gfpSub(&c.x, t6, t) + + // Set y = -(2h)³(s1 + s*(x/4h²-u1)) + // This is also + // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j + gfpSub(t, v, &c.x) // t7 + gfpMul(t4, s1, j) // t8 + gfpAdd(t6, t4, t4) // t9 + gfpMul(t4, r, t) // t10 + gfpSub(&c.y, t4, t6) + + // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2 + gfpAdd(t, &a.z, &b.z) // t11 + gfpMul(t4, t, t) // t12 + gfpSub(t, t4, z12) // t13 + gfpSub(t4, t, z22) // t14 + gfpMul(&c.z, t4, h) +} + +func (c *curvePoint) Double(a *curvePoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A, B, C := &gfP{}, &gfP{}, &gfP{} + gfpMul(A, &a.x, &a.x) + gfpMul(B, &a.y, &a.y) + gfpMul(C, B, B) + + t, t2 := &gfP{}, &gfP{} + gfpAdd(t, &a.x, B) + gfpMul(t2, t, t) + gfpSub(t, t2, A) + gfpSub(t2, t, C) + + d, e, f := &gfP{}, &gfP{}, &gfP{} + gfpAdd(d, t2, t2) + gfpAdd(t, A, A) + gfpAdd(e, t, A) + gfpMul(f, e, e) + + gfpAdd(t, d, d) + gfpSub(&c.x, f, t) + + gfpAdd(t, C, C) + gfpAdd(t2, t, t) + gfpAdd(t, t2, t2) + gfpSub(&c.y, d, &c.x) + gfpMul(t2, e, &c.y) + gfpSub(&c.y, t2, t) + + gfpMul(t, &a.y, &a.z) + gfpAdd(&c.z, t, t) +} + +func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int) { + precomp := [1 << 2]*curvePoint{nil, {}, {}, {}} + precomp[1].Set(a) + precomp[2].Set(a) + gfpMul(&precomp[2].x, &precomp[2].x, xiTo2PSquaredMinus2Over3) + precomp[3].Add(precomp[1], precomp[2]) + + multiScalar := curveLattice.Multi(scalar) + + sum := &curvePoint{} + sum.SetInfinity() + t := &curvePoint{} + + for i := len(multiScalar) - 1; i >= 0; i-- { + t.Double(sum) + if multiScalar[i] == 0 { + sum.Set(t) + } else { + sum.Add(t, precomp[multiScalar[i]]) + } + } + c.Set(sum) +} + +func (c *curvePoint) MakeAffine() { + if c.z == *newGFp(1) { + return + } else if c.z == *newGFp(0) { + c.x = gfP{0} + c.y = *newGFp(1) + c.t = gfP{0} + return + } + + zInv := &gfP{} + zInv.Invert(&c.z) + + t, zInv2 := &gfP{}, &gfP{} + gfpMul(t, &c.y, zInv) + gfpMul(zInv2, zInv, zInv) + + gfpMul(&c.x, &c.x, zInv2) + gfpMul(&c.y, t, zInv2) + + c.z = *newGFp(1) + c.t = *newGFp(1) +} + +func (c *curvePoint) Neg(a *curvePoint) { + c.x.Set(&a.x) + gfpNeg(&c.y, &a.y) + c.z.Set(&a.z) + c.t = gfP{0} +} diff --git a/common/crypto/bn256/cloudflare/example_test.go b/common/crypto/bn256/cloudflare/example_test.go new file mode 100644 index 0000000..6c28599 --- /dev/null +++ b/common/crypto/bn256/cloudflare/example_test.go @@ -0,0 +1,51 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExamplePair(t *testing.T) { + // This implements the tripartite Diffie-Hellman algorithm from "A One + // Round Protocol for Tripartite Diffie-Hellman", A. Joux. + // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf + + // Each of three parties, a, b and c, generate a private value. + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + // Then each party calculates g₁ and g₂ times their private value. + pa := new(G1).ScalarBaseMult(a) + qa := new(G2).ScalarBaseMult(a) + + pb := new(G1).ScalarBaseMult(b) + qb := new(G2).ScalarBaseMult(b) + + pc := new(G1).ScalarBaseMult(c) + qc := new(G2).ScalarBaseMult(c) + + // Now each party exchanges its public values with the other two and + // all parties can calculate the shared key. + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + + // k1, k2 and k3 will all be equal. + + require.Equal(t, k1, k2) + require.Equal(t, k1, k3) + + require.Equal(t, len(np), 4) //Avoid gometalinter varcheck err on np +} diff --git a/common/crypto/bn256/cloudflare/gfp.go b/common/crypto/bn256/cloudflare/gfp.go new file mode 100644 index 0000000..842b190 --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp.go @@ -0,0 +1,97 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +import ( + "errors" + "fmt" +) + +type gfP [4]uint64 + +func newGFp(x int64) (out *gfP) { + if x >= 0 { + out = &gfP{uint64(x)} + } else { + out = &gfP{uint64(-x)} + gfpNeg(out, out) + } + + montEncode(out, out) + return out +} + +func (e *gfP) String() string { + return fmt.Sprintf("%16.16x%16.16x%16.16x%16.16x", e[3], e[2], e[1], e[0]) +} + +func (e *gfP) Set(f *gfP) { + e[0] = f[0] + e[1] = f[1] + e[2] = f[2] + e[3] = f[3] +} + +func (e *gfP) Invert(f *gfP) { + bits := [4]uint64{0x3c208c16d87cfd45, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + + sum, power := &gfP{}, &gfP{} + sum.Set(rN1) + power.Set(f) + + for word := 0; word < 4; word++ { + for bit := uint(0); bit < 64; bit++ { + if (bits[word]>>bit)&1 == 1 { + gfpMul(sum, sum, power) + } + gfpMul(power, power, power) + } + } + + gfpMul(sum, sum, r3) + e.Set(sum) +} + +func (e *gfP) Marshal(out []byte) { + for w := uint(0); w < 4; w++ { + for b := uint(0); b < 8; b++ { + out[8*w+b] = byte(e[3-w] >> (56 - 8*b)) + } + } +} + +func (e *gfP) Unmarshal(in []byte) error { + // Unmarshal the bytes into little endian form + for w := uint(0); w < 4; w++ { + for b := uint(0); b < 8; b++ { + e[3-w] += uint64(in[8*w+b]) << (56 - 8*b) + } + } + // Ensure the point respects the curve modulus + for i := 3; i >= 0; i-- { + if e[i] < p2[i] { + return nil + } + if e[i] > p2[i] { + return errors.New("bn256: coordinate exceeds modulus") + } + } + return errors.New("bn256: coordinate equals modulus") +} + +func montEncode(c, a *gfP) { gfpMul(c, a, r2) } +func montDecode(c, a *gfP) { gfpMul(c, a, &gfP{1}) } diff --git a/common/crypto/bn256/cloudflare/gfp12.go b/common/crypto/bn256/cloudflare/gfp12.go new file mode 100644 index 0000000..9e8a5cd --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp12.go @@ -0,0 +1,176 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP12 implements the field of size p¹² as a quadratic extension of gfP6 +// where ω²=τ. +type gfP12 struct { + x, y gfP6 // value is xω + y +} + +func (e *gfP12) String() string { + return "(" + e.x.String() + "," + e.y.String() + ")" +} + +func (e *gfP12) Set(a *gfP12) *gfP12 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) SetZero() *gfP12 { + e.x.SetZero() + e.y.SetZero() + return e +} + +func (e *gfP12) SetOne() *gfP12 { + e.x.SetZero() + e.y.SetOne() + return e +} + +func (e *gfP12) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() +} + +func (e *gfP12) IsOne() bool { + return e.x.IsZero() && e.y.IsOne() +} + +func (e *gfP12) Conjugate(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) Neg(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + return e +} + +// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p +func (e *gfP12) Frobenius(a *gfP12) *gfP12 { + e.x.Frobenius(&a.x) + e.y.Frobenius(&a.y) + e.x.MulScalar(&e.x, xiToPMinus1Over6) + return e +} + +// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² +func (e *gfP12) FrobeniusP2(a *gfP12) *gfP12 { + e.x.FrobeniusP2(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over6) + e.y.FrobeniusP2(&a.y) + return e +} + +func (e *gfP12) FrobeniusP4(a *gfP12) *gfP12 { + e.x.FrobeniusP4(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over3) + e.y.FrobeniusP4(&a.y) + return e +} + +func (e *gfP12) Add(a, b *gfP12) *gfP12 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + return e +} + +func (e *gfP12) Sub(a, b *gfP12) *gfP12 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + return e +} + +func (e *gfP12) Mul(a, b *gfP12) *gfP12 { + tx := (&gfP6{}).Mul(&a.x, &b.y) + t := (&gfP6{}).Mul(&b.x, &a.y) + tx.Add(tx, t) + + ty := (&gfP6{}).Mul(&a.y, &b.y) + t.Mul(&a.x, &b.x).MulTau(t) + + e.x.Set(tx) + e.y.Add(ty, t) + return e +} + +func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 { + e.x.Mul(&e.x, b) + e.y.Mul(&e.y, b) + return e +} + +func (c *gfP12) Exp(a *gfP12, power *big.Int) *gfP12 { + sum := (&gfP12{}).SetOne() + t := &gfP12{} + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum) + if power.Bit(i) != 0 { + sum.Mul(t, a) + } else { + sum.Set(t) + } + } + + c.Set(sum) + return c +} + +func (e *gfP12) Square(a *gfP12) *gfP12 { + // Complex squaring algorithm + v0 := (&gfP6{}).Mul(&a.x, &a.y) + + t := (&gfP6{}).MulTau(&a.x) + t.Add(&a.y, t) + ty := (&gfP6{}).Add(&a.x, &a.y) + ty.Mul(ty, t).Sub(ty, v0) + t.MulTau(v0) + ty.Sub(ty, t) + + e.x.Add(v0, v0) + e.y.Set(ty) + return e +} + +func (e *gfP12) Invert(a *gfP12) *gfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP6{}, &gfP6{} + + t1.Square(&a.x) + t2.Square(&a.y) + t1.MulTau(t1).Sub(t2, t1) + t2.Invert(t1) + + e.x.Neg(&a.x) + e.y.Set(&a.y) + e.MulScalar(e, t2) + return e +} diff --git a/common/crypto/bn256/cloudflare/gfp2.go b/common/crypto/bn256/cloudflare/gfp2.go new file mode 100644 index 0000000..3294979 --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp2.go @@ -0,0 +1,172 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP2 implements a field of size p² as a quadratic extension of the base field +// where i²=-1. +type gfP2 struct { + x, y gfP // value is xi+y. +} + +func gfP2Decode(in *gfP2) *gfP2 { + out := &gfP2{} + montDecode(&out.x, &in.x) + montDecode(&out.y, &in.y) + return out +} + +func (e *gfP2) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ")" +} + +func (e *gfP2) Set(a *gfP2) *gfP2 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP2) SetZero() *gfP2 { + e.x = gfP{0} + e.y = gfP{0} + return e +} + +func (e *gfP2) SetOne() *gfP2 { + e.x = gfP{0} + e.y = *newGFp(1) + return e +} + +func (e *gfP2) IsZero() bool { + zero := gfP{0} + return e.x == zero && e.y == zero +} + +func (e *gfP2) IsOne() bool { + zero, one := gfP{0}, *newGFp(1) + return e.x == zero && e.y == one +} + +func (e *gfP2) Conjugate(a *gfP2) *gfP2 { + e.y.Set(&a.y) + gfpNeg(&e.x, &a.x) + return e +} + +func (e *gfP2) Neg(a *gfP2) *gfP2 { + gfpNeg(&e.x, &a.x) + gfpNeg(&e.y, &a.y) + return e +} + +func (e *gfP2) Add(a, b *gfP2) *gfP2 { + gfpAdd(&e.x, &a.x, &b.x) + gfpAdd(&e.y, &a.y, &b.y) + return e +} + +func (e *gfP2) Sub(a, b *gfP2) *gfP2 { + gfpSub(&e.x, &a.x, &b.x) + gfpSub(&e.y, &a.y, &b.y) + return e +} + +// See "Multiplication and Squaring in Pairing-Friendly Fields", +// http://eprint.iacr.org/2006/471.pdf +func (e *gfP2) Mul(a, b *gfP2) *gfP2 { + tx, t := &gfP{}, &gfP{} + gfpMul(tx, &a.x, &b.y) + gfpMul(t, &b.x, &a.y) + gfpAdd(tx, tx, t) + + ty := &gfP{} + gfpMul(ty, &a.y, &b.y) + gfpMul(t, &a.x, &b.x) + gfpSub(ty, ty, t) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) MulScalar(a *gfP2, b *gfP) *gfP2 { + gfpMul(&e.x, &a.x, b) + gfpMul(&e.y, &a.y, b) + return e +} + +// MulXi sets e=ξa where ξ=i+9 and then returns e. +func (e *gfP2) MulXi(a *gfP2) *gfP2 { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + tx := &gfP{} + gfpAdd(tx, &a.x, &a.x) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, &a.x) + + gfpAdd(tx, tx, &a.y) + + ty := &gfP{} + gfpAdd(ty, &a.y, &a.y) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, &a.y) + + gfpSub(ty, ty, &a.x) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Square(a *gfP2) *gfP2 { + // Complex squaring algorithm: + // (xi+y)² = (x+y)(y-x) + 2*i*x*y + tx, ty := &gfP{}, &gfP{} + gfpSub(tx, &a.y, &a.x) + gfpAdd(ty, &a.x, &a.y) + gfpMul(ty, tx, ty) + + gfpMul(tx, &a.x, &a.y) + gfpAdd(tx, tx, tx) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Invert(a *gfP2) *gfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP{}, &gfP{} + gfpMul(t1, &a.x, &a.x) + gfpMul(t2, &a.y, &a.y) + gfpAdd(t1, t1, t2) + + inv := &gfP{} + inv.Invert(t1) + + gfpNeg(t1, &a.x) + + gfpMul(&e.x, t1, inv) + gfpMul(&e.y, &a.y, inv) + return e +} diff --git a/common/crypto/bn256/cloudflare/gfp6.go b/common/crypto/bn256/cloudflare/gfp6.go new file mode 100644 index 0000000..8cbbcd3 --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp6.go @@ -0,0 +1,229 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP6 implements the field of size p⁶ as a cubic extension of gfP2 where τ³=ξ +// and ξ=i+9. +type gfP6 struct { + x, y, z gfP2 // value is xτ² + yτ + z +} + +func (e *gfP6) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ", " + e.z.String() + ")" +} + +func (e *gfP6) Set(a *gfP6) *gfP6 { + e.x.Set(&a.x) + e.y.Set(&a.y) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) SetZero() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetZero() + return e +} + +func (e *gfP6) SetOne() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetOne() + return e +} + +func (e *gfP6) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsZero() +} + +func (e *gfP6) IsOne() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsOne() +} + +func (e *gfP6) Neg(a *gfP6) *gfP6 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + e.z.Neg(&a.z) + return e +} + +func (e *gfP6) Frobenius(a *gfP6) *gfP6 { + e.x.Conjugate(&a.x) + e.y.Conjugate(&a.y) + e.z.Conjugate(&a.z) + + e.x.Mul(&e.x, xiTo2PMinus2Over3) + e.y.Mul(&e.y, xiToPMinus1Over3) + return e +} + +// FrobeniusP2 computes (xτ²+yτ+z)^(p²) = xτ^(2p²) + yτ^(p²) + z +func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { + // τ^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + e.x.MulScalar(&a.x, xiTo2PSquaredMinus2Over3) + // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) + e.y.MulScalar(&a.y, xiToPSquaredMinus1Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) FrobeniusP4(a *gfP6) *gfP6 { + e.x.MulScalar(&a.x, xiToPSquaredMinus1Over3) + e.y.MulScalar(&a.y, xiTo2PSquaredMinus2Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) Add(a, b *gfP6) *gfP6 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + e.z.Add(&a.z, &b.z) + return e +} + +func (e *gfP6) Sub(a, b *gfP6) *gfP6 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + e.z.Sub(&a.z, &b.z) + return e +} + +func (e *gfP6) Mul(a, b *gfP6) *gfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + v0 := (&gfP2{}).Mul(&a.z, &b.z) + v1 := (&gfP2{}).Mul(&a.y, &b.y) + v2 := (&gfP2{}).Mul(&a.x, &b.x) + + t0 := (&gfP2{}).Add(&a.x, &a.y) + t1 := (&gfP2{}).Add(&b.x, &b.y) + tz := (&gfP2{}).Mul(t0, t1) + tz.Sub(tz, v1).Sub(tz, v2).MulXi(tz).Add(tz, v0) + + t0.Add(&a.y, &a.z) + t1.Add(&b.y, &b.z) + ty := (&gfP2{}).Mul(t0, t1) + t0.MulXi(v2) + ty.Sub(ty, v0).Sub(ty, v1).Add(ty, t0) + + t0.Add(&a.x, &a.z) + t1.Add(&b.x, &b.z) + tx := (&gfP2{}).Mul(t0, t1) + tx.Sub(tx, v0).Add(tx, v1).Sub(tx, v2) + + e.x.Set(tx) + e.y.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) MulScalar(a *gfP6, b *gfP2) *gfP6 { + e.x.Mul(&a.x, b) + e.y.Mul(&a.y, b) + e.z.Mul(&a.z, b) + return e +} + +func (e *gfP6) MulGFP(a *gfP6, b *gfP) *gfP6 { + e.x.MulScalar(&a.x, b) + e.y.MulScalar(&a.y, b) + e.z.MulScalar(&a.z, b) + return e +} + +// MulTau computes τ·(aτ²+bτ+c) = bτ²+cτ+aξ +func (e *gfP6) MulTau(a *gfP6) *gfP6 { + tz := (&gfP2{}).MulXi(&a.x) + ty := (&gfP2{}).Set(&a.y) + + e.y.Set(&a.z) + e.x.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) Square(a *gfP6) *gfP6 { + v0 := (&gfP2{}).Square(&a.z) + v1 := (&gfP2{}).Square(&a.y) + v2 := (&gfP2{}).Square(&a.x) + + c0 := (&gfP2{}).Add(&a.x, &a.y) + c0.Square(c0).Sub(c0, v1).Sub(c0, v2).MulXi(c0).Add(c0, v0) + + c1 := (&gfP2{}).Add(&a.y, &a.z) + c1.Square(c1).Sub(c1, v0).Sub(c1, v1) + xiV2 := (&gfP2{}).MulXi(v2) + c1.Add(c1, xiV2) + + c2 := (&gfP2{}).Add(&a.x, &a.z) + c2.Square(c2).Sub(c2, v0).Add(c2, v1).Sub(c2, v2) + + e.x.Set(c2) + e.y.Set(c1) + e.z.Set(c0) + return e +} + +func (e *gfP6) Invert(a *gfP6) *gfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = (xτ² + yτ + z)(Cτ²+Bτ+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + τ(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + t1 := (&gfP2{}).Mul(&a.x, &a.y) + t1.MulXi(t1) + + A := (&gfP2{}).Square(&a.z) + A.Sub(A, t1) + + B := (&gfP2{}).Square(&a.x) + B.MulXi(B) + t1.Mul(&a.y, &a.z) + B.Sub(B, t1) + + C := (&gfP2{}).Square(&a.y) + t1.Mul(&a.x, &a.z) + C.Sub(C, t1) + + F := (&gfP2{}).Mul(C, &a.y) + F.MulXi(F) + t1.Mul(A, &a.z) + F.Add(F, t1) + t1.Mul(B, &a.x).MulXi(t1) + F.Add(F, t1) + + F.Invert(F) + + e.x.Mul(C, F) + e.y.Mul(B, F) + e.z.Mul(A, F) + return e +} diff --git a/common/crypto/bn256/cloudflare/gfp_amd64.s b/common/crypto/bn256/cloudflare/gfp_amd64.s new file mode 100644 index 0000000..bdb4ffb --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp_amd64.s @@ -0,0 +1,129 @@ +// +build amd64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVQ a0, 0+r \ + MOVQ a1, 8+r \ + MOVQ a2, 16+r \ + MOVQ a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVQ 0+r, a0 \ + MOVQ 8+r, a1 \ + MOVQ 16+r, a2 \ + MOVQ 24+r, a3 + +#define gfpCarry(a0,a1,a2,a3,a4, b0,b1,b2,b3,b4) \ + \ // b = a-p + MOVQ a0, b0 \ + MOVQ a1, b1 \ + MOVQ a2, b2 \ + MOVQ a3, b3 \ + MOVQ a4, b4 \ + \ + SUBQ ·p2+0(SB), b0 \ + SBBQ ·p2+8(SB), b1 \ + SBBQ ·p2+16(SB), b2 \ + SBBQ ·p2+24(SB), b3 \ + SBBQ $0, b4 \ + \ + \ // if b is negative then return a + \ // else return b + CMOVQCC b0, a0 \ + CMOVQCC b1, a1 \ + CMOVQCC b2, a2 \ + CMOVQCC b3, a3 + +#include "mul_amd64.h" +#include "mul_bmi2_amd64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVQ ·p2+0(SB), R8 + MOVQ ·p2+8(SB), R9 + MOVQ ·p2+16(SB), R10 + MOVQ ·p2+24(SB), R11 + + MOVQ a+8(FP), DI + SUBQ 0(DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + + MOVQ $0, AX + gfpCarry(R8,R9,R10,R11,AX, R12,R13,R14,R15,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + MOVQ $0, R12 + + ADDQ 0(SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ $0, R12 + + gfpCarry(R8,R9,R10,R11,R12, R13,R14,R15,AX,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + + MOVQ ·p2+0(SB), R12 + MOVQ ·p2+8(SB), R13 + MOVQ ·p2+16(SB), R14 + MOVQ ·p2+24(SB), R15 + MOVQ $0, AX + + SUBQ 0(SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + + CMOVQCC AX, R12 + CMOVQCC AX, R13 + CMOVQCC AX, R14 + CMOVQCC AX, R15 + + ADDQ R12, R8 + ADCQ R13, R9 + ADCQ R14, R10 + ADCQ R15, R11 + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpMul(SB),0,$160-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // Jump to a slightly different implementation if MULX isn't supported. + CMPB ·hasBMI2(SB), $0 + JE nobmi2Mul + + mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI)) + storeBlock( R8, R9,R10,R11, 0(SP)) + storeBlock(R12,R13,R14,R15, 32(SP)) + gfpReduceBMI2() + JMP end + +nobmi2Mul: + mul(0(DI),8(DI),16(DI),24(DI), 0(SI), 0(SP)) + gfpReduce(0(SP)) + +end: + MOVQ c+0(FP), DI + storeBlock(R12,R13,R14,R15, 0(DI)) + RET diff --git a/common/crypto/bn256/cloudflare/gfp_arm64.s b/common/crypto/bn256/cloudflare/gfp_arm64.s new file mode 100644 index 0000000..c65e801 --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp_arm64.s @@ -0,0 +1,113 @@ +// +build arm64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVD a0, 0+r \ + MOVD a1, 8+r \ + MOVD a2, 16+r \ + MOVD a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVD 0+r, a0 \ + MOVD 8+r, a1 \ + MOVD 16+r, a2 \ + MOVD 24+r, a3 + +#define loadModulus(p0,p1,p2,p3) \ + MOVD ·p2+0(SB), p0 \ + MOVD ·p2+8(SB), p1 \ + MOVD ·p2+16(SB), p2 \ + MOVD ·p2+24(SB), p3 + +#include "mul_arm64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + loadModulus(R5,R6,R7,R8) + + SUBS R1, R5, R1 + SBCS R2, R6, R2 + SBCS R3, R7, R3 + SBCS R4, R8, R4 + + SUBS R5, R1, R5 + SBCS R6, R2, R6 + SBCS R7, R3, R7 + SBCS R8, R4, R8 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + MOVD ZR, R0 + + ADDS R5, R1 + ADCS R6, R2 + ADCS R7, R3 + ADCS R8, R4 + ADCS ZR, R0 + + SUBS R9, R1, R5 + SBCS R10, R2, R6 + SBCS R11, R3, R7 + SBCS R12, R4, R8 + SBCS ZR, R0, R0 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + + SUBS R5, R1 + SBCS R6, R2 + SBCS R7, R3 + SBCS R8, R4 + + CSEL CS, ZR, R9, R9 + CSEL CS, ZR, R10, R10 + CSEL CS, ZR, R11, R11 + CSEL CS, ZR, R12, R12 + + ADDS R9, R1 + ADCS R10, R2 + ADCS R11, R3 + ADCS R12, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpMul(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + + mul(R9,R10,R11,R12,R13,R14,R15,R16) + gfpReduce() + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET diff --git a/common/crypto/bn256/cloudflare/gfp_decl.go b/common/crypto/bn256/cloudflare/gfp_decl.go new file mode 100644 index 0000000..072e32b --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp_decl.go @@ -0,0 +1,24 @@ +//go:build (amd64 && !generic) || (arm64 && !generic) + +package bn256 + +// This file contains forward declarations for the architecture-specific +// assembly implementations of these functions, provided that they exist. + +import ( + "golang.org/x/sys/cpu" +) + +var hasBMI2 = cpu.X86.HasBMI2 + +// go:noescape +func gfpNeg(c, a *gfP) + +//go:noescape +func gfpAdd(c, a, b *gfP) + +//go:noescape +func gfpSub(c, a, b *gfP) + +//go:noescape +func gfpMul(c, a, b *gfP) diff --git a/common/crypto/bn256/cloudflare/gfp_generic.go b/common/crypto/bn256/cloudflare/gfp_generic.go new file mode 100644 index 0000000..944208c --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp_generic.go @@ -0,0 +1,173 @@ +//go:build (!amd64 && !arm64) || generic + +package bn256 + +func gfpCarry(a *gfP, head uint64) { + b := &gfP{} + + var carry uint64 + for i, pi := range p2 { + ai := a[i] + bi := ai - pi - carry + b[i] = bi + carry = (pi&^ai | (pi|^ai)&bi) >> 63 + } + carry = carry &^ head + + // If b is negative, then return a. + // Else return b. + carry = -carry + ncarry := ^carry + for i := 0; i < 4; i++ { + a[i] = (a[i] & carry) | (b[i] & ncarry) + } +} + +func gfpNeg(c, a *gfP) { + var carry uint64 + for i, pi := range p2 { + ai := a[i] + ci := pi - ai - carry + c[i] = ci + carry = (ai&^pi | (ai|^pi)&ci) >> 63 + } + gfpCarry(c, 0) +} + +func gfpAdd(c, a, b *gfP) { + var carry uint64 + for i, ai := range a { + bi := b[i] + ci := ai + bi + carry + c[i] = ci + carry = (ai&bi | (ai|bi)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func gfpSub(c, a, b *gfP) { + t := &gfP{} + + var carry uint64 + for i, pi := range p2 { + bi := b[i] + ti := pi - bi - carry + t[i] = ti + carry = (bi&^pi | (bi|^pi)&ti) >> 63 + } + + carry = 0 + for i, ai := range a { + ti := t[i] + ci := ai + ti + carry + c[i] = ci + carry = (ai&ti | (ai|ti)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func mul(a, b [4]uint64) [8]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [32]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 8; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [8]uint64{buff[0], buff[4], buff[8], buff[12], buff[16], buff[20], buff[24], buff[28]} +} + +func halfMul(a, b [4]uint64) [4]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [18]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + if i+j > 3 { + break + } + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 4; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [4]uint64{buff[0], buff[4], buff[8], buff[12]} +} + +func gfpMul(c, a, b *gfP) { + T := mul(*a, *b) + m := halfMul([4]uint64{T[0], T[1], T[2], T[3]}, np) + t := mul([4]uint64{m[0], m[1], m[2], m[3]}, p2) + + var carry uint64 + for i, Ti := range T { + ti := t[i] + zi := Ti + ti + carry + T[i] = zi + carry = (Ti&ti | (Ti|ti)&^zi) >> 63 + } + + *c = gfP{T[4], T[5], T[6], T[7]} + gfpCarry(c, carry) +} diff --git a/common/crypto/bn256/cloudflare/gfp_test.go b/common/crypto/bn256/cloudflare/gfp_test.go new file mode 100644 index 0000000..16ab2a8 --- /dev/null +++ b/common/crypto/bn256/cloudflare/gfp_test.go @@ -0,0 +1,60 @@ +package bn256 + +import ( + "testing" +) + +// Tests that negation works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpNeg(t *testing.T) { + n := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + w := &gfP{0xfedcba9876543211, 0x0123456789abcdef, 0x2152411021524110, 0x0114251201142512} + h := &gfP{} + + gfpNeg(h, n) + if *h != *w { + t.Errorf("negation mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that addition works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpAdd(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xc3df73e9278302b8, 0x687e956e978e3572, 0x254954275c18417f, 0xad354b6afc67f9b4} + h := &gfP{} + + gfpAdd(h, a, b) + if *h != *w { + t.Errorf("addition mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that subtraction works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpSub(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0x02468acf13579bdf, 0xfdb97530eca86420, 0xdfc1e401dfc1e402, 0x203e1bfe203e1bfd} + h := &gfP{} + + gfpSub(h, a, b) + if *h != *w { + t.Errorf("subtraction mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that multiplication works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpMul(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xcbcbd377f7ad22d3, 0x3b89ba5d849379bf, 0x87b61627bd38b6d2, 0xc44052a2a0e654b2} + h := &gfP{} + + gfpMul(h, a, b) + if *h != *w { + t.Errorf("multiplication mismatch: have %#x, want %#x", *h, *w) + } +} diff --git a/common/crypto/bn256/cloudflare/lattice.go b/common/crypto/bn256/cloudflare/lattice.go new file mode 100644 index 0000000..f9ace4d --- /dev/null +++ b/common/crypto/bn256/cloudflare/lattice.go @@ -0,0 +1,115 @@ +package bn256 + +import ( + "math/big" +) + +var half = new(big.Int).Rsh(Order, 1) + +var curveLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("147946756881789319000765030803803410728"), bigFromBase10("147946756881789319010696353538189108491")}, + {bigFromBase10("147946756881789319020627676272574806254"), bigFromBase10("-147946756881789318990833708069417712965")}, + }, + inverse: []*big.Int{ + bigFromBase10("147946756881789318990833708069417712965"), + bigFromBase10("147946756881789319010696353538189108491"), + }, + det: bigFromBase10("43776485743678550444492811490514550177096728800832068687396408373151616991234"), +} + +var targetLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697763"), bigFromBase10("9931322734385697764")}, + {bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848882"), bigFromBase10("-9931322734385697762")}, + {bigFromBase10("-9931322734385697762"), bigFromBase10("-4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("-4965661367192848882")}, + {bigFromBase10("9931322734385697763"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881")}, + }, + inverse: []*big.Int{ + bigFromBase10("734653495049373973658254490726798021314063399421879442165"), + bigFromBase10("147946756881789319000765030803803410728"), + bigFromBase10("-147946756881789319005730692170996259609"), + bigFromBase10("1469306990098747947464455738335385361643788813749140841702"), + }, + det: new(big.Int).Set(Order), +} + +type lattice struct { + vectors [][]*big.Int + inverse []*big.Int + det *big.Int +} + +// decompose takes a scalar mod Order as input and finds a short, positive decomposition of it wrt to the lattice basis. +func (l *lattice) decompose(k *big.Int) []*big.Int { + n := len(l.inverse) + + // Calculate closest vector in lattice to with Babai's rounding. + c := make([]*big.Int, n) + for i := 0; i < n; i++ { + c[i] = new(big.Int).Mul(k, l.inverse[i]) + round(c[i], l.det) + } + + // Transform vectors according to c and subtract . + out := make([]*big.Int, n) + temp := new(big.Int) + + for i := 0; i < n; i++ { + out[i] = new(big.Int) + + for j := 0; j < n; j++ { + temp.Mul(c[j], l.vectors[j][i]) + out[i].Add(out[i], temp) + } + + out[i].Neg(out[i]) + out[i].Add(out[i], l.vectors[0][i]).Add(out[i], l.vectors[0][i]) + } + out[0].Add(out[0], k) + + return out +} + +func (l *lattice) Precompute(add func(i, j uint)) { + n := uint(len(l.vectors)) + total := uint(1) << n + + for i := uint(0); i < n; i++ { + for j := uint(0); j < total; j++ { + if (j>>i)&1 == 1 { + add(i, j) + } + } + } +} + +func (l *lattice) Multi(scalar *big.Int) []uint8 { + decomp := l.decompose(scalar) + + maxLen := 0 + for _, x := range decomp { + if x.BitLen() > maxLen { + maxLen = x.BitLen() + } + } + + out := make([]uint8, maxLen) + for j, x := range decomp { + for i := 0; i < maxLen; i++ { + out[i] += uint8(x.Bit(i)) << uint(j) + } + } + + return out +} + +// round sets num to num/denom rounded to the nearest integer. +func round(num, denom *big.Int) { + r := new(big.Int) + num.DivMod(num, denom, r) + + if r.Cmp(half) == 1 { + num.Add(num, big.NewInt(1)) + } +} diff --git a/common/crypto/bn256/cloudflare/lattice_test.go b/common/crypto/bn256/cloudflare/lattice_test.go new file mode 100644 index 0000000..4d52ad9 --- /dev/null +++ b/common/crypto/bn256/cloudflare/lattice_test.go @@ -0,0 +1,29 @@ +package bn256 + +import ( + "crypto/rand" + + "testing" +) + +func TestLatticeReduceCurve(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := curveLattice.decompose(k) + + if ks[0].BitLen() > 130 || ks[1].BitLen() > 130 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} + +func TestLatticeReduceTarget(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := targetLattice.decompose(k) + + if ks[0].BitLen() > 66 || ks[1].BitLen() > 66 || ks[2].BitLen() > 66 || ks[3].BitLen() > 66 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 || ks[2].Sign() < 0 || ks[3].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} diff --git a/common/crypto/bn256/cloudflare/main_test.go b/common/crypto/bn256/cloudflare/main_test.go new file mode 100644 index 0000000..f0610ed --- /dev/null +++ b/common/crypto/bn256/cloudflare/main_test.go @@ -0,0 +1,72 @@ +package bn256 + +import ( + "testing" + + "crypto/rand" +) + +func TestRandomG2Marshal(t *testing.T) { + for i := 0; i < 10; i++ { + n, g2, err := RandomG2(rand.Reader) + if err != nil { + t.Error(err) + continue + } + t.Logf("%v: %x\n", n, g2.Marshal()) + } +} + +func TestPairings(t *testing.T) { + _ = hasBMI2 + a1 := new(G1).ScalarBaseMult(bigFromBase10("1")) + a2 := new(G1).ScalarBaseMult(bigFromBase10("2")) + a37 := new(G1).ScalarBaseMult(bigFromBase10("37")) + an1 := new(G1).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + b0 := new(G2).ScalarBaseMult(bigFromBase10("0")) + b1 := new(G2).ScalarBaseMult(bigFromBase10("1")) + b2 := new(G2).ScalarBaseMult(bigFromBase10("2")) + b27 := new(G2).ScalarBaseMult(bigFromBase10("27")) + b999 := new(G2).ScalarBaseMult(bigFromBase10("999")) + bn1 := new(G2).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + p1 := Pair(a1, b1) + pn1 := Pair(a1, bn1) + np1 := Pair(an1, b1) + if pn1.String() != np1.String() { + t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") + } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false negative!") + } + p0 := new(GT).Add(p1, pn1) + p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { + t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") + } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + if p0.String() != p0_3.String() { + t.Error("Pairing mismatch: e(a, b) has wrong order") + } + p2 := Pair(a2, b1) + p2_2 := Pair(a1, b2) + p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") + } + if p2.String() != p2_3.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") + } + if p2.String() == p1.String() { + t.Error("Pairing is degenerate!") + } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false positive!") + } + p999 := Pair(a37, b27) + p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { + t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") + } +} diff --git a/common/crypto/bn256/cloudflare/mul_amd64.h b/common/crypto/bn256/cloudflare/mul_amd64.h new file mode 100644 index 0000000..bab5da8 --- /dev/null +++ b/common/crypto/bn256/cloudflare/mul_amd64.h @@ -0,0 +1,181 @@ +#define mul(a0,a1,a2,a3, rb, stack) \ + MOVQ a0, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a0, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a0, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a0, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + storeBlock(R8,R9,R10,R11, 0+stack) \ + MOVQ R12, 32+stack \ + \ + MOVQ a1, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a1, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a1, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a1, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 8+stack, R8 \ + ADCQ 16+stack, R9 \ + ADCQ 24+stack, R10 \ + ADCQ 32+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 8+stack) \ + MOVQ R12, 40+stack \ + \ + MOVQ a2, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a2, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a2, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a2, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 16+stack, R8 \ + ADCQ 24+stack, R9 \ + ADCQ 32+stack, R10 \ + ADCQ 40+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 16+stack) \ + MOVQ R12, 48+stack \ + \ + MOVQ a3, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a3, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a3, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a3, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 24+stack, R8 \ + ADCQ 32+stack, R9 \ + ADCQ 40+stack, R10 \ + ADCQ 48+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 24+stack) \ + MOVQ R12, 56+stack + +#define gfpReduce(stack) \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ ·np+0(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ ·np+0(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ ·np+0(SB), AX \ + MULQ 24+stack \ + ADDQ AX, R11 \ + \ + MOVQ ·np+8(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+8(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + MOVQ ·np+8(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R14 \ + \ + ADDQ R12, R9 \ + ADCQ R13, R10 \ + ADCQ R14, R11 \ + \ + MOVQ ·np+16(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+16(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + \ + ADDQ R12, R10 \ + ADCQ R13, R11 \ + \ + MOVQ ·np+24(SB), AX \ + MULQ 0+stack \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64+stack) \ + \ + \ // m * N + mul(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64+stack, 96+stack) \ + \ + \ // Add the 512-bit intermediate to m*N + loadBlock(96+stack, R8,R9,R10,R11) \ + loadBlock(128+stack, R12,R13,R14,R15) \ + \ + MOVQ $0, AX \ + ADDQ 0+stack, R8 \ + ADCQ 8+stack, R9 \ + ADCQ 16+stack, R10 \ + ADCQ 24+stack, R11 \ + ADCQ 32+stack, R12 \ + ADCQ 40+stack, R13 \ + ADCQ 48+stack, R14 \ + ADCQ 56+stack, R15 \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,R15,AX, R8,R9,R10,R11,BX) diff --git a/common/crypto/bn256/cloudflare/mul_arm64.h b/common/crypto/bn256/cloudflare/mul_arm64.h new file mode 100644 index 0000000..d405eb8 --- /dev/null +++ b/common/crypto/bn256/cloudflare/mul_arm64.h @@ -0,0 +1,133 @@ +#define mul(c0,c1,c2,c3,c4,c5,c6,c7) \ + MUL R1, R5, c0 \ + UMULH R1, R5, c1 \ + MUL R1, R6, R0 \ + ADDS R0, c1 \ + UMULH R1, R6, c2 \ + MUL R1, R7, R0 \ + ADCS R0, c2 \ + UMULH R1, R7, c3 \ + MUL R1, R8, R0 \ + ADCS R0, c3 \ + UMULH R1, R8, c4 \ + ADCS ZR, c4 \ + \ + MUL R2, R5, R1 \ + UMULH R2, R5, R26 \ + MUL R2, R6, R0 \ + ADDS R0, R26 \ + UMULH R2, R6, R27 \ + MUL R2, R7, R0 \ + ADCS R0, R27 \ + UMULH R2, R7, R29 \ + MUL R2, R8, R0 \ + ADCS R0, R29 \ + UMULH R2, R8, c5 \ + ADCS ZR, c5 \ + ADDS R1, c1 \ + ADCS R26, c2 \ + ADCS R27, c3 \ + ADCS R29, c4 \ + ADCS ZR, c5 \ + \ + MUL R3, R5, R1 \ + UMULH R3, R5, R26 \ + MUL R3, R6, R0 \ + ADDS R0, R26 \ + UMULH R3, R6, R27 \ + MUL R3, R7, R0 \ + ADCS R0, R27 \ + UMULH R3, R7, R29 \ + MUL R3, R8, R0 \ + ADCS R0, R29 \ + UMULH R3, R8, c6 \ + ADCS ZR, c6 \ + ADDS R1, c2 \ + ADCS R26, c3 \ + ADCS R27, c4 \ + ADCS R29, c5 \ + ADCS ZR, c6 \ + \ + MUL R4, R5, R1 \ + UMULH R4, R5, R26 \ + MUL R4, R6, R0 \ + ADDS R0, R26 \ + UMULH R4, R6, R27 \ + MUL R4, R7, R0 \ + ADCS R0, R27 \ + UMULH R4, R7, R29 \ + MUL R4, R8, R0 \ + ADCS R0, R29 \ + UMULH R4, R8, c7 \ + ADCS ZR, c7 \ + ADDS R1, c3 \ + ADCS R26, c4 \ + ADCS R27, c5 \ + ADCS R29, c6 \ + ADCS ZR, c7 + +#define gfpReduce() \ + \ // m = (T * N') mod R, store m in R1:R2:R3:R4 + MOVD ·np+0(SB), R17 \ + MOVD ·np+8(SB), R25 \ + MOVD ·np+16(SB), R19 \ + MOVD ·np+24(SB), R20 \ + \ + MUL R9, R17, R1 \ + UMULH R9, R17, R2 \ + MUL R9, R25, R0 \ + ADDS R0, R2 \ + UMULH R9, R25, R3 \ + MUL R9, R19, R0 \ + ADCS R0, R3 \ + UMULH R9, R19, R4 \ + MUL R9, R20, R0 \ + ADCS R0, R4 \ + \ + MUL R10, R17, R21 \ + UMULH R10, R17, R22 \ + MUL R10, R25, R0 \ + ADDS R0, R22 \ + UMULH R10, R25, R23 \ + MUL R10, R19, R0 \ + ADCS R0, R23 \ + ADDS R21, R2 \ + ADCS R22, R3 \ + ADCS R23, R4 \ + \ + MUL R11, R17, R21 \ + UMULH R11, R17, R22 \ + MUL R11, R25, R0 \ + ADDS R0, R22 \ + ADDS R21, R3 \ + ADCS R22, R4 \ + \ + MUL R12, R17, R21 \ + ADDS R21, R4 \ + \ + \ // m * N + loadModulus(R5,R6,R7,R8) \ + mul(R17,R25,R19,R20,R21,R22,R23,R24) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVD ZR, R0 \ + ADDS R9, R17 \ + ADCS R10, R25 \ + ADCS R11, R19 \ + ADCS R12, R20 \ + ADCS R13, R21 \ + ADCS R14, R22 \ + ADCS R15, R23 \ + ADCS R16, R24 \ + ADCS ZR, R0 \ + \ + \ // Our output is R21:R22:R23:R24. Reduce mod p if necessary. + SUBS R5, R21, R10 \ + SBCS R6, R22, R11 \ + SBCS R7, R23, R12 \ + SBCS R8, R24, R13 \ + \ + CSEL CS, R10, R21, R1 \ + CSEL CS, R11, R22, R2 \ + CSEL CS, R12, R23, R3 \ + CSEL CS, R13, R24, R4 diff --git a/common/crypto/bn256/cloudflare/mul_bmi2_amd64.h b/common/crypto/bn256/cloudflare/mul_bmi2_amd64.h new file mode 100644 index 0000000..71ad049 --- /dev/null +++ b/common/crypto/bn256/cloudflare/mul_bmi2_amd64.h @@ -0,0 +1,112 @@ +#define mulBMI2(a0,a1,a2,a3, rb) \ + MOVQ a0, DX \ + MOVQ $0, R13 \ + MULXQ 0+rb, R8, R9 \ + MULXQ 8+rb, AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16+rb, AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24+rb, AX, R12 \ + ADCQ AX, R11 \ + ADCQ $0, R12 \ + ADCQ $0, R13 \ + \ + MOVQ a1, DX \ + MOVQ $0, R14 \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R11 \ + ADCQ BX, R12 \ + ADCQ $0, R13 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + \ + MOVQ a2, DX \ + MOVQ $0, R15 \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, R15 \ + \ + MOVQ a3, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, R15 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R15 + +#define gfpReduceBMI2() \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), DX \ + MULXQ 0(SP), R8, R9 \ + MULXQ 8(SP), AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16(SP), AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24(SP), AX, BX \ + ADCQ AX, R11 \ + \ + MOVQ ·np+8(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16(SP), AX, BX \ + ADCQ AX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + \ + MOVQ ·np+16(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R11 \ + \ + MOVQ ·np+24(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64(SP)) \ + \ + \ // m * N + mulBMI2(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64(SP)) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVQ $0, AX \ + ADDQ 0(SP), R8 \ + ADCQ 8(SP), R9 \ + ADCQ 16(SP), R10 \ + ADCQ 24(SP), R11 \ + ADCQ 32(SP), R12 \ + ADCQ 40(SP), R13 \ + ADCQ 48(SP), R14 \ + ADCQ 56(SP), R15 \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,R15,AX, R8,R9,R10,R11,BX) diff --git a/common/crypto/bn256/cloudflare/optate.go b/common/crypto/bn256/cloudflare/optate.go new file mode 100644 index 0000000..25418e8 --- /dev/null +++ b/common/crypto/bn256/cloudflare/optate.go @@ -0,0 +1,286 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2) (a, b, c *gfP2, rOut *twistPoint) { + // See the mixed addition algorithm from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + B := (&gfP2{}).Mul(&p.x, &r.t) + + D := (&gfP2{}).Add(&p.y, &r.z) + D.Square(D).Sub(D, r2).Sub(D, &r.t).Mul(D, &r.t) + + H := (&gfP2{}).Sub(B, &r.x) + I := (&gfP2{}).Square(H) + + E := (&gfP2{}).Add(I, I) + E.Add(E, E) + + J := (&gfP2{}).Mul(H, E) + + L1 := (&gfP2{}).Sub(D, &r.y) + L1.Sub(L1, &r.y) + + V := (&gfP2{}).Mul(&r.x, E) + + rOut = &twistPoint{} + rOut.x.Square(L1).Sub(&rOut.x, J).Sub(&rOut.x, V).Sub(&rOut.x, V) + + rOut.z.Add(&r.z, H).Square(&rOut.z).Sub(&rOut.z, &r.t).Sub(&rOut.z, I) + + t := (&gfP2{}).Sub(V, &rOut.x) + t.Mul(t, L1) + t2 := (&gfP2{}).Mul(&r.y, J) + t2.Add(t2, t2) + rOut.y.Sub(t, t2) + + rOut.t.Square(&rOut.z) + + t.Add(&p.y, &rOut.z).Square(t).Sub(t, r2).Sub(t, &rOut.t) + + t2.Mul(L1, &p.x) + t2.Add(t2, t2) + a = (&gfP2{}).Sub(t2, t) + + c = (&gfP2{}).MulScalar(&rOut.z, &q.y) + c.Add(c, c) + + b = (&gfP2{}).Neg(L1) + b.MulScalar(b, &q.x).Add(b, b) + + return +} + +func lineFunctionDouble(r *twistPoint, q *curvePoint) (a, b, c *gfP2, rOut *twistPoint) { + // See the doubling algorithm for a=0 from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + A := (&gfP2{}).Square(&r.x) + B := (&gfP2{}).Square(&r.y) + C := (&gfP2{}).Square(B) + + D := (&gfP2{}).Add(&r.x, B) + D.Square(D).Sub(D, A).Sub(D, C).Add(D, D) + + E := (&gfP2{}).Add(A, A) + E.Add(E, A) + + G := (&gfP2{}).Square(E) + + rOut = &twistPoint{} + rOut.x.Sub(G, D).Sub(&rOut.x, D) + + rOut.z.Add(&r.y, &r.z).Square(&rOut.z).Sub(&rOut.z, B).Sub(&rOut.z, &r.t) + + rOut.y.Sub(D, &rOut.x).Mul(&rOut.y, E) + t := (&gfP2{}).Add(C, C) + t.Add(t, t).Add(t, t) + rOut.y.Sub(&rOut.y, t) + + rOut.t.Square(&rOut.z) + + t.Mul(E, &r.t).Add(t, t) + b = (&gfP2{}).Neg(t) + b.MulScalar(b, &q.x) + + a = (&gfP2{}).Add(&r.x, E) + a.Square(a).Sub(a, A).Sub(a, G) + t.Add(B, B).Add(t, t) + a.Sub(a, t) + + c = (&gfP2{}).Mul(&rOut.z, &r.t) + c.Add(c, c).MulScalar(c, &q.y) + + return +} + +func mulLine(ret *gfP12, a, b, c *gfP2) { + a2 := &gfP6{} + a2.y.Set(a) + a2.z.Set(b) + a2.Mul(a2, &ret.x) + t3 := (&gfP6{}).MulScalar(&ret.y, c) + + t := (&gfP2{}).Add(b, c) + t2 := &gfP6{} + t2.y.Set(a) + t2.z.Set(t) + ret.x.Add(&ret.x, &ret.y) + + ret.y.Set(t3) + + ret.x.Mul(&ret.x, t2).Sub(&ret.x, a2).Sub(&ret.x, &ret.y) + a2.MulTau(a2) + ret.y.Add(&ret.y, a2) +} + +// sixuPlus2NAF is 6u+2 in non-adjacent form. +var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1} + +// miller implements the Miller loop for calculating the Optimal Ate pairing. +// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf +func miller(q *twistPoint, p *curvePoint) *gfP12 { + ret := (&gfP12{}).SetOne() + + aAffine := &twistPoint{} + aAffine.Set(q) + aAffine.MakeAffine() + + bAffine := &curvePoint{} + bAffine.Set(p) + bAffine.MakeAffine() + + minusA := &twistPoint{} + minusA.Neg(aAffine) + + r := &twistPoint{} + r.Set(aAffine) + + r2 := (&gfP2{}).Square(&aAffine.y) + + for i := len(sixuPlus2NAF) - 1; i > 0; i-- { + a, b, c, newR := lineFunctionDouble(r, bAffine) + if i != len(sixuPlus2NAF)-1 { + ret.Square(ret) + } + + mulLine(ret, a, b, c) + r = newR + + switch sixuPlus2NAF[i-1] { + case 1: + a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2) + case -1: + a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2) + default: + continue + } + + mulLine(ret, a, b, c) + r = newR + } + + // In order to calculate Q1 we have to convert q from the sextic twist + // to the full GF(p^12) group, apply the Frobenius there, and convert + // back. + // + // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just + // x for a moment, then after applying the Frobenius, we have x̄ω^(2p) + // where x̄ is the conjugate of x. If we are going to apply the inverse + // isomorphism we need a value with a single coefficient of ω² so we + // rewrite this as x̄ω^(2p-2)ω². ξ⁶ = ω and, due to the construction of + // p, 2p-2 is a multiple of six. Therefore we can rewrite as + // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the + // ω². + // + // A similar argument can be made for the y value. + + q1 := &twistPoint{} + q1.x.Conjugate(&aAffine.x).Mul(&q1.x, xiToPMinus1Over3) + q1.y.Conjugate(&aAffine.y).Mul(&q1.y, xiToPMinus1Over2) + q1.z.SetOne() + q1.t.SetOne() + + // For Q2 we are applying the p² Frobenius. The two conjugations cancel + // out and we are left only with the factors from the isomorphism. In + // the case of x, we end up with a pure number which is why + // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We + // ignore this to end up with -Q2. + + minusQ2 := &twistPoint{} + minusQ2.x.MulScalar(&aAffine.x, xiToPSquaredMinus1Over3) + minusQ2.y.Set(&aAffine.y) + minusQ2.z.SetOne() + minusQ2.t.SetOne() + + r2.Square(&q1.y) + a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2) + mulLine(ret, a, b, c) + r = newR + + r2.Square(&minusQ2.y) + a, b, c, _ = lineFunctionAdd(r, minusQ2, bAffine, r2) + mulLine(ret, a, b, c) + + return ret +} + +// finalExponentiation computes the (p¹²-1)/Order-th power of an element of +// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from +// http://cryptojedi.org/papers/dclxvi-20100714.pdf) +func finalExponentiation(in *gfP12) *gfP12 { + t1 := &gfP12{} + + // This is the p^6-Frobenius + t1.x.Neg(&in.x) + t1.y.Set(&in.y) + + inv := &gfP12{} + inv.Invert(in) + t1.Mul(t1, inv) + + t2 := (&gfP12{}).FrobeniusP2(t1) + t1.Mul(t1, t2) + + fp := (&gfP12{}).Frobenius(t1) + fp2 := (&gfP12{}).FrobeniusP2(t1) + fp3 := (&gfP12{}).Frobenius(fp2) + + fu := (&gfP12{}).Exp(t1, u) + fu2 := (&gfP12{}).Exp(fu, u) + fu3 := (&gfP12{}).Exp(fu2, u) + + y3 := (&gfP12{}).Frobenius(fu) + fu2p := (&gfP12{}).Frobenius(fu2) + fu3p := (&gfP12{}).Frobenius(fu3) + y2 := (&gfP12{}).FrobeniusP2(fu2) + + y0 := &gfP12{} + y0.Mul(fp, fp2).Mul(y0, fp3) + + y1 := (&gfP12{}).Conjugate(t1) + y5 := (&gfP12{}).Conjugate(fu2) + y3.Conjugate(y3) + y4 := (&gfP12{}).Mul(fu, fu2p) + y4.Conjugate(y4) + + y6 := (&gfP12{}).Mul(fu3, fu3p) + y6.Conjugate(y6) + + t0 := (&gfP12{}).Square(y6) + t0.Mul(t0, y4).Mul(t0, y5) + t1.Mul(y3, y5).Mul(t1, t0) + t0.Mul(t0, y2) + t1.Square(t1).Mul(t1, t0).Square(t1) + t0.Mul(t1, y1) + t1.Mul(t1, y0) + t0.Square(t0).Mul(t0, t1) + + return t0 +} + +func optimalAte(a *twistPoint, b *curvePoint) *gfP12 { + e := miller(a, b) + ret := finalExponentiation(e) + + if a.IsInfinity() || b.IsInfinity() { + ret.SetOne() + } + return ret +} diff --git a/common/crypto/bn256/cloudflare/twist.go b/common/crypto/bn256/cloudflare/twist.go new file mode 100644 index 0000000..a3798d5 --- /dev/null +++ b/common/crypto/bn256/cloudflare/twist.go @@ -0,0 +1,220 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package bn256 + +import ( + "math/big" +) + +// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are +// kept in Jacobian form and t=z² when valid. The group G₂ is the set of +// n-torsion points of this curve over GF(p²) (where n = Order) +type twistPoint struct { + x, y, z, t gfP2 +} + +var twistB = &gfP2{ + gfP{0x38e7ecccd1dcff67, 0x65f0b37d93ce0d3e, 0xd749d0dd22ac00aa, 0x0141b9ce4a688d4d}, + gfP{0x3bf938e377b802a8, 0x020b1b273633535d, 0x26b7edf049755260, 0x2514c6324384a86d}, +} + +// twistGen is the generator of group G₂. +var twistGen = &twistPoint{ + gfP2{ + gfP{0xafb4737da84c6140, 0x6043dd5a5802d8c4, 0x09e950fc52a02f86, 0x14fef0833aea7b6b}, + gfP{0x8e83b5d102bc2026, 0xdceb1935497b0172, 0xfbb8264797811adf, 0x19573841af96503b}, + }, + gfP2{ + gfP{0x64095b56c71856ee, 0xdc57f922327d3cbb, 0x55f935be33351076, 0x0da4a0e693fd6482}, + gfP{0x619dfa9d886be9f6, 0xfe7fd297f59e9b78, 0xff9e1a62231b7dfe, 0x28fd7eebae9e4206}, + }, + gfP2{*newGFp(0), *newGFp(1)}, + gfP2{*newGFp(0), *newGFp(1)}, +} + +func (c *twistPoint) String() string { + c.MakeAffine() + x, y := gfP2Decode(&c.x), gfP2Decode(&c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *twistPoint) Set(a *twistPoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *twistPoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP2{}, &gfP2{} + y2.Square(&c.y) + x3.Square(&c.x).Mul(x3, &c.x).Add(x3, twistB) + + if *y2 != *x3 { + return false + } + cneg := &twistPoint{} + cneg.Mul(c, Order) + return cneg.z.IsZero() +} + +func (c *twistPoint) SetInfinity() { + c.x.SetZero() + c.y.SetOne() + c.z.SetZero() + c.t.SetZero() +} + +func (c *twistPoint) IsInfinity() bool { + return c.z.IsZero() +} + +func (c *twistPoint) Add(a, b *twistPoint) { + // For additional comments, see the same function in curve.go. + + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + z12 := (&gfP2{}).Square(&a.z) + z22 := (&gfP2{}).Square(&b.z) + u1 := (&gfP2{}).Mul(&a.x, z22) + u2 := (&gfP2{}).Mul(&b.x, z12) + + t := (&gfP2{}).Mul(&b.z, z22) + s1 := (&gfP2{}).Mul(&a.y, t) + + t.Mul(&a.z, z12) + s2 := (&gfP2{}).Mul(&b.y, t) + + h := (&gfP2{}).Sub(u2, u1) + xEqual := h.IsZero() + + t.Add(h, h) + i := (&gfP2{}).Square(t) + j := (&gfP2{}).Mul(h, i) + + t.Sub(s2, s1) + yEqual := t.IsZero() + if xEqual && yEqual { + c.Double(a) + return + } + r := (&gfP2{}).Add(t, t) + + v := (&gfP2{}).Mul(u1, i) + + t4 := (&gfP2{}).Square(r) + t.Add(v, v) + t6 := (&gfP2{}).Sub(t4, j) + c.x.Sub(t6, t) + + t.Sub(v, &c.x) // t7 + t4.Mul(s1, j) // t8 + t6.Add(t4, t4) // t9 + t4.Mul(r, t) // t10 + c.y.Sub(t4, t6) + + t.Add(&a.z, &b.z) // t11 + t4.Square(t) // t12 + t.Sub(t4, z12) // t13 + t4.Sub(t, z22) // t14 + c.z.Mul(t4, h) +} + +func (c *twistPoint) Double(a *twistPoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := (&gfP2{}).Square(&a.x) + B := (&gfP2{}).Square(&a.y) + C := (&gfP2{}).Square(B) + + t := (&gfP2{}).Add(&a.x, B) + t2 := (&gfP2{}).Square(t) + t.Sub(t2, A) + t2.Sub(t, C) + d := (&gfP2{}).Add(t2, t2) + t.Add(A, A) + e := (&gfP2{}).Add(t, A) + f := (&gfP2{}).Square(e) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C, C) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, &c.x) + t2.Mul(e, &c.y) + c.y.Sub(t2, t) + + t.Mul(&a.y, &a.z) + c.z.Add(t, t) +} + +func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int) { + sum, t := &twistPoint{}, &twistPoint{} + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum) + if scalar.Bit(i) != 0 { + sum.Add(t, a) + } else { + sum.Set(t) + } + } + + c.Set(sum) +} + +func (c *twistPoint) MakeAffine() { + if c.z.IsOne() { + return + } else if c.z.IsZero() { + c.x.SetZero() + c.y.SetOne() + c.t.SetZero() + return + } + + zInv := (&gfP2{}).Invert(&c.z) + t := (&gfP2{}).Mul(&c.y, zInv) + zInv2 := (&gfP2{}).Square(zInv) + c.y.Mul(t, zInv2) + t.Mul(&c.x, zInv2) + c.x.Set(t) + c.z.SetOne() + c.t.SetOne() +} + +func (c *twistPoint) Neg(a *twistPoint) { + c.x.Set(&a.x) + c.y.Neg(&a.y) + c.z.Set(&a.z) + c.t.SetZero() +} diff --git a/common/crypto/bn256/google/bn256.go b/common/crypto/bn256/google/bn256.go new file mode 100644 index 0000000..0a9d5cd --- /dev/null +++ b/common/crypto/bn256/google/bn256.go @@ -0,0 +1,460 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bn256 implements a particular bilinear group. +// +// Bilinear groups are the basis of many of the new cryptographic protocols +// that have been proposed over the past decade. They consist of a triplet of +// groups (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ +// (where gₓ is a generator of the respective group). That function is called +// a pairing function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) +package bn256 + +import ( + "crypto/rand" + "errors" + "io" + "math/big" +) + +// BUG(agl): this implementation is not constant time. +// TODO(agl): keep GF(p²) elements in Mongomery form. + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 struct { + p *curvePoint +} + +// RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r. +func RandomG1(r io.Reader) (*big.Int, *G1, error) { + var k *big.Int + var err error + + for { + k, err = rand.Int(r, Order) + if err != nil { + return nil, nil, err + } + if k.Sign() > 0 { + break + } + } + + return k, new(G1).ScalarBaseMult(k), nil +} + +func (e *G1) String() string { + return "bn256.G1" + e.p.String() +} + +// CurvePoints returns p's curve points in big integer +func (e *G1) CurvePoints() (*big.Int, *big.Int, *big.Int, *big.Int) { + return e.p.x, e.p.y, e.p.z, e.p.t +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and +// then returns e. +func (e *G1) ScalarBaseMult(k *big.Int) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Mul(curveGen, k, new(bnPool)) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Mul(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +// BUG(agl): this function is not complete: a==b fails. +func (e *G1) Add(a, b *G1) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Add(a.p, b.p, new(bnPool)) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G1) Neg(a *G1) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Negative(a.p) + return e +} + +// Marshal converts n to a byte slice. +func (e *G1) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p.IsInfinity() { + return make([]byte, numBytes*2) + } + + e.p.MakeAffine(nil) + + xBytes := new(big.Int).Mod(e.p.x, P).Bytes() + yBytes := new(big.Int).Mod(e.p.y, P).Bytes() + + ret := make([]byte, numBytes*2) + copy(ret[1*numBytes-len(xBytes):], xBytes) + copy(ret[2*numBytes-len(yBytes):], yBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G1) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) != 2*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + // Ensure the point is on the curve + if e.p.x.Sign() == 0 && e.p.y.Sign() == 0 { + // This is the point at infinity. + e.p.y.SetInt64(1) + e.p.z.SetInt64(0) + e.p.t.SetInt64(0) + } else { + e.p.z.SetInt64(1) + e.p.t.SetInt64(1) + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[2*numBytes:], nil +} + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 struct { + p *twistPoint +} + +// RandomG1 returns x and g₂ˣ where x is a random, non-zero number read from r. +func RandomG2(r io.Reader) (*big.Int, *G2, error) { + var k *big.Int + var err error + + for { + k, err = rand.Int(r, Order) + if err != nil { + return nil, nil, err + } + if k.Sign() > 0 { + break + } + } + + return k, new(G2).ScalarBaseMult(k), nil +} + +func (e *G2) String() string { + return "bn256.G2" + e.p.String() +} + +// CurvePoints returns the curve points of p which includes the real +// and imaginary parts of the curve point. +func (e *G2) CurvePoints() (*gfP2, *gfP2, *gfP2, *gfP2) { + return e.p.x, e.p.y, e.p.z, e.p.t +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and +// then returns out. +func (e *G2) ScalarBaseMult(k *big.Int) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Mul(twistGen, k, new(bnPool)) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Mul(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +// BUG(agl): this function is not complete: a==b fails. +func (e *G2) Add(a, b *G2) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Add(a.p, b.p, new(bnPool)) + return e +} + +// Marshal converts n into a byte slice. +func (n *G2) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if n.p.IsInfinity() { + return make([]byte, numBytes*4) + } + + n.p.MakeAffine(nil) + + xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes() + xyBytes := new(big.Int).Mod(n.p.x.y, P).Bytes() + yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes() + yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes() + + ret := make([]byte, numBytes*4) + copy(ret[1*numBytes-len(xxBytes):], xxBytes) + copy(ret[2*numBytes-len(xyBytes):], xyBytes) + copy(ret[3*numBytes-len(yxBytes):], yxBytes) + copy(ret[4*numBytes-len(yyBytes):], yyBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G2) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) != 4*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.x.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.x.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.x.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.x.SetBytes(m[2*numBytes : 3*numBytes]) + if e.p.y.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.y.SetBytes(m[3*numBytes : 4*numBytes]) + if e.p.y.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + // Ensure the point is on the curve + if e.p.x.x.Sign() == 0 && + e.p.x.y.Sign() == 0 && + e.p.y.x.Sign() == 0 && + e.p.y.y.Sign() == 0 { + // This is the point at infinity. + e.p.y.SetOne() + e.p.z.SetZero() + e.p.t.SetZero() + } else { + e.p.z.SetOne() + e.p.t.SetOne() + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[4*numBytes:], nil +} + +// GT is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type GT struct { + p *gfP12 +} + +func (g *GT) String() string { + return "bn256.GT" + g.p.String() +} + +// ScalarMult sets e to a*k and then returns e. +func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Exp(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +func (e *GT) Add(a, b *GT) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Mul(a.p, b.p, new(bnPool)) + return e +} + +// Neg sets e to -a and then returns e. +func (e *GT) Neg(a *GT) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Invert(a.p, new(bnPool)) + return e +} + +// Marshal converts n into a byte slice. +func (n *GT) Marshal() []byte { + n.p.Minimal() + + xxxBytes := n.p.x.x.x.Bytes() + xxyBytes := n.p.x.x.y.Bytes() + xyxBytes := n.p.x.y.x.Bytes() + xyyBytes := n.p.x.y.y.Bytes() + xzxBytes := n.p.x.z.x.Bytes() + xzyBytes := n.p.x.z.y.Bytes() + yxxBytes := n.p.y.x.x.Bytes() + yxyBytes := n.p.y.x.y.Bytes() + yyxBytes := n.p.y.y.x.Bytes() + yyyBytes := n.p.y.y.y.Bytes() + yzxBytes := n.p.y.z.x.Bytes() + yzyBytes := n.p.y.z.y.Bytes() + + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + ret := make([]byte, numBytes*12) + copy(ret[1*numBytes-len(xxxBytes):], xxxBytes) + copy(ret[2*numBytes-len(xxyBytes):], xxyBytes) + copy(ret[3*numBytes-len(xyxBytes):], xyxBytes) + copy(ret[4*numBytes-len(xyyBytes):], xyyBytes) + copy(ret[5*numBytes-len(xzxBytes):], xzxBytes) + copy(ret[6*numBytes-len(xzyBytes):], xzyBytes) + copy(ret[7*numBytes-len(yxxBytes):], yxxBytes) + copy(ret[8*numBytes-len(yxyBytes):], yxyBytes) + copy(ret[9*numBytes-len(yyxBytes):], yyxBytes) + copy(ret[10*numBytes-len(yyyBytes):], yyyBytes) + copy(ret[11*numBytes-len(yzxBytes):], yzxBytes) + copy(ret[12*numBytes-len(yzyBytes):], yzyBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *GT) Unmarshal(m []byte) (*GT, bool) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if len(m) != 12*numBytes { + return nil, false + } + + if e.p == nil { + e.p = newGFp12(nil) + } + + e.p.x.x.x.SetBytes(m[0*numBytes : 1*numBytes]) + e.p.x.x.y.SetBytes(m[1*numBytes : 2*numBytes]) + e.p.x.y.x.SetBytes(m[2*numBytes : 3*numBytes]) + e.p.x.y.y.SetBytes(m[3*numBytes : 4*numBytes]) + e.p.x.z.x.SetBytes(m[4*numBytes : 5*numBytes]) + e.p.x.z.y.SetBytes(m[5*numBytes : 6*numBytes]) + e.p.y.x.x.SetBytes(m[6*numBytes : 7*numBytes]) + e.p.y.x.y.SetBytes(m[7*numBytes : 8*numBytes]) + e.p.y.y.x.SetBytes(m[8*numBytes : 9*numBytes]) + e.p.y.y.y.SetBytes(m[9*numBytes : 10*numBytes]) + e.p.y.z.x.SetBytes(m[10*numBytes : 11*numBytes]) + e.p.y.z.y.SetBytes(m[11*numBytes : 12*numBytes]) + + return e, true +} + +// Pair calculates an Optimal Ate pairing. +func Pair(g1 *G1, g2 *G2) *GT { + return >{optimalAte(g2.p, g1.p, new(bnPool))} +} + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + pool := new(bnPool) + + acc := newGFp12(pool) + acc.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].p.IsInfinity() || b[i].p.IsInfinity() { + continue + } + acc.Mul(acc, miller(b[i].p, a[i].p, pool), pool) + } + ret := finalExponentiation(acc, pool) + acc.Put(pool) + + return ret.IsOne() +} + +// bnPool implements a tiny cache of *big.Int objects that's used to reduce the +// number of allocations made during processing. +type bnPool struct { + bns []*big.Int + count int +} + +func (pool *bnPool) Get() *big.Int { + if pool == nil { + return new(big.Int) + } + + pool.count++ + l := len(pool.bns) + if l == 0 { + return new(big.Int) + } + + bn := pool.bns[l-1] + pool.bns = pool.bns[:l-1] + return bn +} + +func (pool *bnPool) Put(bn *big.Int) { + if pool == nil { + return + } + pool.bns = append(pool.bns, bn) + pool.count-- +} + +func (pool *bnPool) Count() int { + return pool.count +} diff --git a/common/crypto/bn256/google/bn256_test.go b/common/crypto/bn256/google/bn256_test.go new file mode 100644 index 0000000..a4497ad --- /dev/null +++ b/common/crypto/bn256/google/bn256_test.go @@ -0,0 +1,311 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +func TestGFp2Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp2(pool) + a.x.SetString("23423492374", 10) + a.y.SetString("12934872398472394827398470", 10) + + inv := newGFp2(pool) + inv.Invert(a, pool) + + b := newGFp2(pool).Mul(inv, a, pool) + if b.x.Int64() != 0 || b.y.Int64() != 1 { + t.Fatalf("bad result for a^-1*a: %s %s", b.x, b.y) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func isZero(n *big.Int) bool { + return new(big.Int).Mod(n, P).Int64() == 0 +} + +func isOne(n *big.Int) bool { + return new(big.Int).Mod(n, P).Int64() == 1 +} + +func TestGFp6Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp6(pool) + a.x.x.SetString("239487238491", 10) + a.x.y.SetString("2356249827341", 10) + a.y.x.SetString("082659782", 10) + a.y.y.SetString("182703523765", 10) + a.z.x.SetString("978236549263", 10) + a.z.y.SetString("64893242", 10) + + inv := newGFp6(pool) + inv.Invert(a, pool) + + b := newGFp6(pool).Mul(inv, a, pool) + if !isZero(b.x.x) || + !isZero(b.x.y) || + !isZero(b.y.x) || + !isZero(b.y.y) || + !isZero(b.z.x) || + !isOne(b.z.y) { + t.Fatalf("bad result for a^-1*a: %s", b) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestGFp12Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp12(pool) + a.x.x.x.SetString("239846234862342323958623", 10) + a.x.x.y.SetString("2359862352529835623", 10) + a.x.y.x.SetString("928836523", 10) + a.x.y.y.SetString("9856234", 10) + a.x.z.x.SetString("235635286", 10) + a.x.z.y.SetString("5628392833", 10) + a.y.x.x.SetString("252936598265329856238956532167968", 10) + a.y.x.y.SetString("23596239865236954178968", 10) + a.y.y.x.SetString("95421692834", 10) + a.y.y.y.SetString("236548", 10) + a.y.z.x.SetString("924523", 10) + a.y.z.y.SetString("12954623", 10) + + inv := newGFp12(pool) + inv.Invert(a, pool) + + b := newGFp12(pool).Mul(inv, a, pool) + if !isZero(b.x.x.x) || + !isZero(b.x.x.y) || + !isZero(b.x.y.x) || + !isZero(b.x.y.y) || + !isZero(b.x.z.x) || + !isZero(b.x.z.y) || + !isZero(b.y.x.x) || + !isZero(b.y.x.y) || + !isZero(b.y.y.x) || + !isZero(b.y.y.y) || + !isZero(b.y.z.x) || + !isOne(b.y.z.y) { + t.Fatalf("bad result for a^-1*a: %s", b) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestCurveImpl(t *testing.T) { + pool := new(bnPool) + + g := &curvePoint{ + pool.Get().SetInt64(1), + pool.Get().SetInt64(-2), + pool.Get().SetInt64(1), + pool.Get().SetInt64(0), + } + + x := pool.Get().SetInt64(32498273234) + X := newCurvePoint(pool).Mul(g, x, pool) + + y := pool.Get().SetInt64(98732423523) + Y := newCurvePoint(pool).Mul(g, y, pool) + + s1 := newCurvePoint(pool).Mul(X, y, pool).MakeAffine(pool) + s2 := newCurvePoint(pool).Mul(Y, x, pool).MakeAffine(pool) + + if s1.x.Cmp(s2.x) != 0 || + s2.x.Cmp(s1.x) != 0 { + t.Errorf("DH points don't match: (%s, %s) (%s, %s)", s1.x, s1.y, s2.x, s2.y) + } + + pool.Put(x) + X.Put(pool) + pool.Put(y) + Y.Put(pool) + s1.Put(pool) + s2.Put(pool) + g.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestOrderG1(t *testing.T) { + g := new(G1).ScalarBaseMult(Order) + if !g.p.IsInfinity() { + t.Error("G1 has incorrect order") + } + + one := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) + g.Add(g, one) + g.p.MakeAffine(nil) + if g.p.x.Cmp(one.p.x) != 0 || g.p.y.Cmp(one.p.y) != 0 { + t.Errorf("1+0 != 1 in G1") + } +} + +func TestOrderG2(t *testing.T) { + g := new(G2).ScalarBaseMult(Order) + if !g.p.IsInfinity() { + t.Error("G2 has incorrect order") + } + + one := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) + g.Add(g, one) + g.p.MakeAffine(nil) + if g.p.x.x.Cmp(one.p.x.x) != 0 || + g.p.x.y.Cmp(one.p.x.y) != 0 || + g.p.y.x.Cmp(one.p.y.x) != 0 || + g.p.y.y.Cmp(one.p.y.y) != 0 { + t.Errorf("1+0 != 1 in G2") + } +} + +func TestOrderGT(t *testing.T) { + gt := Pair(&G1{curveGen}, &G2{twistGen}) + g := new(GT).ScalarMult(gt, Order) + if !g.p.IsOne() { + t.Error("GT has incorrect order") + } +} + +func TestBilinearity(t *testing.T) { + for i := 0; i < 2; i++ { + a, p1, _ := RandomG1(rand.Reader) + b, p2, _ := RandomG2(rand.Reader) + e1 := Pair(p1, p2) + + e2 := Pair(&G1{curveGen}, &G2{twistGen}) + e2.ScalarMult(e2, a) + e2.ScalarMult(e2, b) + + minusE2 := new(GT).Neg(e2) + e1.Add(e1, minusE2) + + if !e1.p.IsOne() { + t.Fatalf("bad pairing result: %s", e1) + } + } +} + +func TestG1Marshal(t *testing.T) { + g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) + form := g.Marshal() + _, err := new(G1).Unmarshal(form) + if err != nil { + t.Fatalf("failed to unmarshal") + } + + g.ScalarBaseMult(Order) + form = g.Marshal() + + g2 := new(G1) + if _, err = g2.Unmarshal(form); err != nil { + t.Fatalf("failed to unmarshal ∞") + } + if !g2.p.IsInfinity() { + t.Fatalf("∞ unmarshaled incorrectly") + } +} + +func TestG2Marshal(t *testing.T) { + g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) + form := g.Marshal() + _, err := new(G2).Unmarshal(form) + if err != nil { + t.Fatalf("failed to unmarshal") + } + + g.ScalarBaseMult(Order) + form = g.Marshal() + g2 := new(G2) + if _, err = g2.Unmarshal(form); err != nil { + t.Fatalf("failed to unmarshal ∞") + } + if !g2.p.IsInfinity() { + t.Fatalf("∞ unmarshaled incorrectly") + } +} + +func TestG1Identity(t *testing.T) { + g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(0)) + if !g.p.IsInfinity() { + t.Error("failure") + } +} + +func TestG2Identity(t *testing.T) { + g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(0)) + if !g.p.IsInfinity() { + t.Error("failure") + } +} + +func TestTripartiteDiffieHellman(t *testing.T) { + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + pa := new(G1) + pa.Unmarshal(new(G1).ScalarBaseMult(a).Marshal()) + qa := new(G2) + qa.Unmarshal(new(G2).ScalarBaseMult(a).Marshal()) + pb := new(G1) + pb.Unmarshal(new(G1).ScalarBaseMult(b).Marshal()) + qb := new(G2) + qb.Unmarshal(new(G2).ScalarBaseMult(b).Marshal()) + pc := new(G1) + pc.Unmarshal(new(G1).ScalarBaseMult(c).Marshal()) + qc := new(G2) + qc.Unmarshal(new(G2).ScalarBaseMult(c).Marshal()) + + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + k1Bytes := k1.Marshal() + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + k2Bytes := k2.Marshal() + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + k3Bytes := k3.Marshal() + + if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) { + t.Errorf("keys didn't agree") + } +} + +func BenchmarkPairing(b *testing.B) { + for i := 0; i < b.N; i++ { + Pair(&G1{curveGen}, &G2{twistGen}) + } +} diff --git a/common/crypto/bn256/google/constants.go b/common/crypto/bn256/google/constants.go new file mode 100644 index 0000000..2990bd9 --- /dev/null +++ b/common/crypto/bn256/google/constants.go @@ -0,0 +1,47 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +func bigFromBase10(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 10) + return n +} + +// u is the BN parameter that determines the prime. +var u = bigFromBase10("4965661367192848881") + +// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. +var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") + +// Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. +var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. +var xiToPMinus1Over6 = &gfP2{bigFromBase10("16469823323077808223889137241176536799009286646108169935659301613961712198316"), bigFromBase10("8376118865763821496583973867626364092589906065868298776909617916018768340080")} + +// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. +var xiToPMinus1Over3 = &gfP2{bigFromBase10("10307601595873709700152284273816112264069230130616436755625194854815875713954"), bigFromBase10("21575463638280843010398324269430826099269044274347216827212613867836435027261")} + +// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+9. +var xiToPMinus1Over2 = &gfP2{bigFromBase10("3505843767911556378687030309984248845540243509899259641013678093033130930403"), bigFromBase10("2821565182194536844548159561693502659359617185244120367078079554186484126554")} + +// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. +var xiToPSquaredMinus1Over3 = bigFromBase10("21888242871839275220042445260109153167277707414472061641714758635765020556616") + +// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). +var xiTo2PSquaredMinus2Over3 = bigFromBase10("2203960485148121921418603742825762020974279258880205651966") + +// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). +var xiToPSquaredMinus1Over6 = bigFromBase10("21888242871839275220042445260109153167277707414472061641714758635765020556617") + +// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. +var xiTo2PMinus2Over3 = &gfP2{bigFromBase10("19937756971775647987995932169929341994314640652964949448313374472400716661030"), bigFromBase10("2581911344467009335267311115468803099551665605076196740867805258568234346338")} diff --git a/common/crypto/bn256/google/curve.go b/common/crypto/bn256/google/curve.go new file mode 100644 index 0000000..819cb81 --- /dev/null +++ b/common/crypto/bn256/google/curve.go @@ -0,0 +1,286 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +// curvePoint implements the elliptic curve y²=x³+3. Points are kept in +// Jacobian form and t=z² when valid. G₁ is the set of points of this curve on +// GF(p). +type curvePoint struct { + x, y, z, t *big.Int +} + +var curveB = new(big.Int).SetInt64(3) + +// curveGen is the generator of G₁. +var curveGen = &curvePoint{ + new(big.Int).SetInt64(1), + new(big.Int).SetInt64(2), + new(big.Int).SetInt64(1), + new(big.Int).SetInt64(1), +} + +func newCurvePoint(pool *bnPool) *curvePoint { + return &curvePoint{ + pool.Get(), + pool.Get(), + pool.Get(), + pool.Get(), + } +} + +func (c *curvePoint) String() string { + c.MakeAffine(new(bnPool)) + return "(" + c.x.String() + ", " + c.y.String() + ")" +} + +func (c *curvePoint) Put(pool *bnPool) { + pool.Put(c.x) + pool.Put(c.y) + pool.Put(c.z) + pool.Put(c.t) +} + +func (c *curvePoint) Set(a *curvePoint) { + c.x.Set(a.x) + c.y.Set(a.y) + c.z.Set(a.z) + c.t.Set(a.t) +} + +// IsOnCurve returns true iff c is on the curve where c must be in affine form. +func (c *curvePoint) IsOnCurve() bool { + yy := new(big.Int).Mul(c.y, c.y) + xxx := new(big.Int).Mul(c.x, c.x) + xxx.Mul(xxx, c.x) + yy.Sub(yy, xxx) + yy.Sub(yy, curveB) + if yy.Sign() < 0 || yy.Cmp(P) >= 0 { + yy.Mod(yy, P) + } + return yy.Sign() == 0 +} + +func (c *curvePoint) SetInfinity() { + c.z.SetInt64(0) +} + +func (c *curvePoint) IsInfinity() bool { + return c.z.Sign() == 0 +} + +func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + + // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2] + // by [u1:s1:z1·z2] and [u2:s2:z1·z2] + // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ + z1z1 := pool.Get().Mul(a.z, a.z) + z1z1.Mod(z1z1, P) + z2z2 := pool.Get().Mul(b.z, b.z) + z2z2.Mod(z2z2, P) + u1 := pool.Get().Mul(a.x, z2z2) + u1.Mod(u1, P) + u2 := pool.Get().Mul(b.x, z1z1) + u2.Mod(u2, P) + + t := pool.Get().Mul(b.z, z2z2) + t.Mod(t, P) + s1 := pool.Get().Mul(a.y, t) + s1.Mod(s1, P) + + t.Mul(a.z, z1z1) + t.Mod(t, P) + s2 := pool.Get().Mul(b.y, t) + s2.Mod(s2, P) + + // Compute x = (2h)²(s²-u1-u2) + // where s = (s2-s1)/(u2-u1) is the slope of the line through + // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below. + // This is also: + // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1) + // = r² - j - 2v + // with the notations below. + h := pool.Get().Sub(u2, u1) + xEqual := h.Sign() == 0 + + t.Add(h, h) + // i = 4h² + i := pool.Get().Mul(t, t) + i.Mod(i, P) + // j = 4h³ + j := pool.Get().Mul(h, i) + j.Mod(j, P) + + t.Sub(s2, s1) + yEqual := t.Sign() == 0 + if xEqual && yEqual { + c.Double(a, pool) + return + } + r := pool.Get().Add(t, t) + + v := pool.Get().Mul(u1, i) + v.Mod(v, P) + + // t4 = 4(s2-s1)² + t4 := pool.Get().Mul(r, r) + t4.Mod(t4, P) + t.Add(v, v) + t6 := pool.Get().Sub(t4, j) + c.x.Sub(t6, t) + + // Set y = -(2h)³(s1 + s*(x/4h²-u1)) + // This is also + // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j + t.Sub(v, c.x) // t7 + t4.Mul(s1, j) // t8 + t4.Mod(t4, P) + t6.Add(t4, t4) // t9 + t4.Mul(r, t) // t10 + t4.Mod(t4, P) + c.y.Sub(t4, t6) + + // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2 + t.Add(a.z, b.z) // t11 + t4.Mul(t, t) // t12 + t4.Mod(t4, P) + t.Sub(t4, z1z1) // t13 + t4.Sub(t, z2z2) // t14 + c.z.Mul(t4, h) + c.z.Mod(c.z, P) + + pool.Put(z1z1) + pool.Put(z2z2) + pool.Put(u1) + pool.Put(u2) + pool.Put(t) + pool.Put(s1) + pool.Put(s2) + pool.Put(h) + pool.Put(i) + pool.Put(j) + pool.Put(r) + pool.Put(v) + pool.Put(t4) + pool.Put(t6) +} + +func (c *curvePoint) Double(a *curvePoint, pool *bnPool) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := pool.Get().Mul(a.x, a.x) + A.Mod(A, P) + B := pool.Get().Mul(a.y, a.y) + B.Mod(B, P) + C_ := pool.Get().Mul(B, B) + C_.Mod(C_, P) + + t := pool.Get().Add(a.x, B) + t2 := pool.Get().Mul(t, t) + t2.Mod(t2, P) + t.Sub(t2, A) + t2.Sub(t, C_) + d := pool.Get().Add(t2, t2) + t.Add(A, A) + e := pool.Get().Add(t, A) + f := pool.Get().Mul(e, e) + f.Mod(f, P) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C_, C_) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, c.x) + t2.Mul(e, c.y) + t2.Mod(t2, P) + c.y.Sub(t2, t) + + t.Mul(a.y, a.z) + t.Mod(t, P) + c.z.Add(t, t) + + pool.Put(A) + pool.Put(B) + pool.Put(C_) + pool.Put(t) + pool.Put(t2) + pool.Put(d) + pool.Put(e) + pool.Put(f) +} + +func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoint { + sum := newCurvePoint(pool) + sum.SetInfinity() + t := newCurvePoint(pool) + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum, pool) + if scalar.Bit(i) != 0 { + sum.Add(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + sum.Put(pool) + t.Put(pool) + return c +} + +// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets +// c to 0 : 1 : 0. +func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint { + if words := c.z.Bits(); len(words) == 1 && words[0] == 1 { + return c + } + if c.IsInfinity() { + c.x.SetInt64(0) + c.y.SetInt64(1) + c.z.SetInt64(0) + c.t.SetInt64(0) + return c + } + zInv := pool.Get().ModInverse(c.z, P) + t := pool.Get().Mul(c.y, zInv) + t.Mod(t, P) + zInv2 := pool.Get().Mul(zInv, zInv) + zInv2.Mod(zInv2, P) + c.y.Mul(t, zInv2) + c.y.Mod(c.y, P) + t.Mul(c.x, zInv2) + t.Mod(t, P) + c.x.Set(t) + c.z.SetInt64(1) + c.t.SetInt64(1) + + pool.Put(zInv) + pool.Put(t) + pool.Put(zInv2) + + return c +} + +func (c *curvePoint) Negative(a *curvePoint) { + c.x.Set(a.x) + c.y.Neg(a.y) + c.z.Set(a.z) + c.t.SetInt64(0) +} diff --git a/common/crypto/bn256/google/example_test.go b/common/crypto/bn256/google/example_test.go new file mode 100644 index 0000000..b2d1980 --- /dev/null +++ b/common/crypto/bn256/google/example_test.go @@ -0,0 +1,43 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" +) + +func ExamplePair() { + // This implements the tripartite Diffie-Hellman algorithm from "A One + // Round Protocol for Tripartite Diffie-Hellman", A. Joux. + // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf + + // Each of three parties, a, b and c, generate a private value. + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + // Then each party calculates g₁ and g₂ times their private value. + pa := new(G1).ScalarBaseMult(a) + qa := new(G2).ScalarBaseMult(a) + + pb := new(G1).ScalarBaseMult(b) + qb := new(G2).ScalarBaseMult(b) + + pc := new(G1).ScalarBaseMult(c) + qc := new(G2).ScalarBaseMult(c) + + // Now each party exchanges its public values with the other two and + // all parties can calculate the shared key. + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + + // k1, k2 and k3 will all be equal. +} diff --git a/common/crypto/bn256/google/gfp12.go b/common/crypto/bn256/google/gfp12.go new file mode 100644 index 0000000..f084edd --- /dev/null +++ b/common/crypto/bn256/google/gfp12.go @@ -0,0 +1,200 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP12 implements the field of size p¹² as a quadratic extension of gfP6 +// where ω²=τ. +type gfP12 struct { + x, y *gfP6 // value is xω + y +} + +func newGFp12(pool *bnPool) *gfP12 { + return &gfP12{newGFp6(pool), newGFp6(pool)} +} + +func (e *gfP12) String() string { + return "(" + e.x.String() + "," + e.y.String() + ")" +} + +func (e *gfP12) Put(pool *bnPool) { + e.x.Put(pool) + e.y.Put(pool) +} + +func (e *gfP12) Set(a *gfP12) *gfP12 { + e.x.Set(a.x) + e.y.Set(a.y) + return e +} + +func (e *gfP12) SetZero() *gfP12 { + e.x.SetZero() + e.y.SetZero() + return e +} + +func (e *gfP12) SetOne() *gfP12 { + e.x.SetZero() + e.y.SetOne() + return e +} + +func (e *gfP12) Minimal() { + e.x.Minimal() + e.y.Minimal() +} + +func (e *gfP12) IsZero() bool { + e.Minimal() + return e.x.IsZero() && e.y.IsZero() +} + +func (e *gfP12) IsOne() bool { + e.Minimal() + return e.x.IsZero() && e.y.IsOne() +} + +func (e *gfP12) Conjugate(a *gfP12) *gfP12 { + e.x.Negative(a.x) + e.y.Set(a.y) + return a +} + +func (e *gfP12) Negative(a *gfP12) *gfP12 { + e.x.Negative(a.x) + e.y.Negative(a.y) + return e +} + +// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p +func (e *gfP12) Frobenius(a *gfP12, pool *bnPool) *gfP12 { + e.x.Frobenius(a.x, pool) + e.y.Frobenius(a.y, pool) + e.x.MulScalar(e.x, xiToPMinus1Over6, pool) + return e +} + +// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² +func (e *gfP12) FrobeniusP2(a *gfP12, pool *bnPool) *gfP12 { + e.x.FrobeniusP2(a.x) + e.x.MulGFP(e.x, xiToPSquaredMinus1Over6) + e.y.FrobeniusP2(a.y) + return e +} + +func (e *gfP12) Add(a, b *gfP12) *gfP12 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + return e +} + +func (e *gfP12) Sub(a, b *gfP12) *gfP12 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + return e +} + +func (e *gfP12) Mul(a, b *gfP12, pool *bnPool) *gfP12 { + tx := newGFp6(pool) + tx.Mul(a.x, b.y, pool) + t := newGFp6(pool) + t.Mul(b.x, a.y, pool) + tx.Add(tx, t) + + ty := newGFp6(pool) + ty.Mul(a.y, b.y, pool) + t.Mul(a.x, b.x, pool) + t.MulTau(t, pool) + e.y.Add(ty, t) + e.x.Set(tx) + + tx.Put(pool) + ty.Put(pool) + t.Put(pool) + return e +} + +func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 { + e.x.Mul(e.x, b, pool) + e.y.Mul(e.y, b, pool) + return e +} + +func (c *gfP12) Exp(a *gfP12, power *big.Int, pool *bnPool) *gfP12 { + sum := newGFp12(pool) + sum.SetOne() + t := newGFp12(pool) + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum, pool) + if power.Bit(i) != 0 { + sum.Mul(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + + sum.Put(pool) + t.Put(pool) + + return c +} + +func (e *gfP12) Square(a *gfP12, pool *bnPool) *gfP12 { + // Complex squaring algorithm + v0 := newGFp6(pool) + v0.Mul(a.x, a.y, pool) + + t := newGFp6(pool) + t.MulTau(a.x, pool) + t.Add(a.y, t) + ty := newGFp6(pool) + ty.Add(a.x, a.y) + ty.Mul(ty, t, pool) + ty.Sub(ty, v0) + t.MulTau(v0, pool) + ty.Sub(ty, t) + + e.y.Set(ty) + e.x.Double(v0) + + v0.Put(pool) + t.Put(pool) + ty.Put(pool) + + return e +} + +func (e *gfP12) Invert(a *gfP12, pool *bnPool) *gfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1 := newGFp6(pool) + t2 := newGFp6(pool) + + t1.Square(a.x, pool) + t2.Square(a.y, pool) + t1.MulTau(t1, pool) + t1.Sub(t2, t1) + t2.Invert(t1, pool) + + e.x.Negative(a.x) + e.y.Set(a.y) + e.MulScalar(e, t2, pool) + + t1.Put(pool) + t2.Put(pool) + + return e +} diff --git a/common/crypto/bn256/google/gfp2.go b/common/crypto/bn256/google/gfp2.go new file mode 100644 index 0000000..3981f6c --- /dev/null +++ b/common/crypto/bn256/google/gfp2.go @@ -0,0 +1,227 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP2 implements a field of size p² as a quadratic extension of the base +// field where i²=-1. +type gfP2 struct { + x, y *big.Int // value is xi+y. +} + +func newGFp2(pool *bnPool) *gfP2 { + return &gfP2{pool.Get(), pool.Get()} +} + +func (e *gfP2) String() string { + x := new(big.Int).Mod(e.x, P) + y := new(big.Int).Mod(e.y, P) + return "(" + x.String() + "," + y.String() + ")" +} + +func (e *gfP2) Put(pool *bnPool) { + pool.Put(e.x) + pool.Put(e.y) +} + +func (e *gfP2) Set(a *gfP2) *gfP2 { + e.x.Set(a.x) + e.y.Set(a.y) + return e +} + +func (e *gfP2) SetZero() *gfP2 { + e.x.SetInt64(0) + e.y.SetInt64(0) + return e +} + +func (e *gfP2) SetOne() *gfP2 { + e.x.SetInt64(0) + e.y.SetInt64(1) + return e +} + +func (e *gfP2) Minimal() { + if e.x.Sign() < 0 || e.x.Cmp(P) >= 0 { + e.x.Mod(e.x, P) + } + if e.y.Sign() < 0 || e.y.Cmp(P) >= 0 { + e.y.Mod(e.y, P) + } +} + +func (e *gfP2) IsZero() bool { + return e.x.Sign() == 0 && e.y.Sign() == 0 +} + +func (e *gfP2) IsOne() bool { + if e.x.Sign() != 0 { + return false + } + words := e.y.Bits() + return len(words) == 1 && words[0] == 1 +} + +func (e *gfP2) Conjugate(a *gfP2) *gfP2 { + e.y.Set(a.y) + e.x.Neg(a.x) + return e +} + +func (e *gfP2) Negative(a *gfP2) *gfP2 { + e.x.Neg(a.x) + e.y.Neg(a.y) + return e +} + +func (e *gfP2) Add(a, b *gfP2) *gfP2 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + return e +} + +func (e *gfP2) Sub(a, b *gfP2) *gfP2 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + return e +} + +func (e *gfP2) Double(a *gfP2) *gfP2 { + e.x.Lsh(a.x, 1) + e.y.Lsh(a.y, 1) + return e +} + +func (c *gfP2) Exp(a *gfP2, power *big.Int, pool *bnPool) *gfP2 { + sum := newGFp2(pool) + sum.SetOne() + t := newGFp2(pool) + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum, pool) + if power.Bit(i) != 0 { + sum.Mul(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + + sum.Put(pool) + t.Put(pool) + + return c +} + +// See "Multiplication and Squaring in Pairing-Friendly Fields", +// http://eprint.iacr.org/2006/471.pdf +func (e *gfP2) Mul(a, b *gfP2, pool *bnPool) *gfP2 { + tx := pool.Get().Mul(a.x, b.y) + t := pool.Get().Mul(b.x, a.y) + tx.Add(tx, t) + tx.Mod(tx, P) + + ty := pool.Get().Mul(a.y, b.y) + t.Mul(a.x, b.x) + ty.Sub(ty, t) + e.y.Mod(ty, P) + e.x.Set(tx) + + pool.Put(tx) + pool.Put(ty) + pool.Put(t) + + return e +} + +func (e *gfP2) MulScalar(a *gfP2, b *big.Int) *gfP2 { + e.x.Mul(a.x, b) + e.y.Mul(a.y, b) + return e +} + +// MulXi sets e=ξa where ξ=i+9 and then returns e. +func (e *gfP2) MulXi(a *gfP2, pool *bnPool) *gfP2 { + // (xi+y)(i+3) = (9x+y)i+(9y-x) + tx := pool.Get().Lsh(a.x, 3) + tx.Add(tx, a.x) + tx.Add(tx, a.y) + + ty := pool.Get().Lsh(a.y, 3) + ty.Add(ty, a.y) + ty.Sub(ty, a.x) + + e.x.Set(tx) + e.y.Set(ty) + + pool.Put(tx) + pool.Put(ty) + + return e +} + +func (e *gfP2) Square(a *gfP2, pool *bnPool) *gfP2 { + // Complex squaring algorithm: + // (xi+b)² = (x+y)(y-x) + 2*i*x*y + t1 := pool.Get().Sub(a.y, a.x) + t2 := pool.Get().Add(a.x, a.y) + ty := pool.Get().Mul(t1, t2) + ty.Mod(ty, P) + + t1.Mul(a.x, a.y) + t1.Lsh(t1, 1) + + e.x.Mod(t1, P) + e.y.Set(ty) + + pool.Put(t1) + pool.Put(t2) + pool.Put(ty) + + return e +} + +func (e *gfP2) Invert(a *gfP2, pool *bnPool) *gfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t := pool.Get() + t.Mul(a.y, a.y) + t2 := pool.Get() + t2.Mul(a.x, a.x) + t.Add(t, t2) + + inv := pool.Get() + inv.ModInverse(t, P) + + e.x.Neg(a.x) + e.x.Mul(e.x, inv) + e.x.Mod(e.x, P) + + e.y.Mul(a.y, inv) + e.y.Mod(e.y, P) + + pool.Put(t) + pool.Put(t2) + pool.Put(inv) + + return e +} + +func (e *gfP2) Real() *big.Int { + return e.x +} + +func (e *gfP2) Imag() *big.Int { + return e.y +} diff --git a/common/crypto/bn256/google/gfp6.go b/common/crypto/bn256/google/gfp6.go new file mode 100644 index 0000000..2188566 --- /dev/null +++ b/common/crypto/bn256/google/gfp6.go @@ -0,0 +1,296 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP6 implements the field of size p⁶ as a cubic extension of gfP2 where τ³=ξ +// and ξ=i+9. +type gfP6 struct { + x, y, z *gfP2 // value is xτ² + yτ + z +} + +func newGFp6(pool *bnPool) *gfP6 { + return &gfP6{newGFp2(pool), newGFp2(pool), newGFp2(pool)} +} + +func (e *gfP6) String() string { + return "(" + e.x.String() + "," + e.y.String() + "," + e.z.String() + ")" +} + +func (e *gfP6) Put(pool *bnPool) { + e.x.Put(pool) + e.y.Put(pool) + e.z.Put(pool) +} + +func (e *gfP6) Set(a *gfP6) *gfP6 { + e.x.Set(a.x) + e.y.Set(a.y) + e.z.Set(a.z) + return e +} + +func (e *gfP6) SetZero() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetZero() + return e +} + +func (e *gfP6) SetOne() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetOne() + return e +} + +func (e *gfP6) Minimal() { + e.x.Minimal() + e.y.Minimal() + e.z.Minimal() +} + +func (e *gfP6) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsZero() +} + +func (e *gfP6) IsOne() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsOne() +} + +func (e *gfP6) Negative(a *gfP6) *gfP6 { + e.x.Negative(a.x) + e.y.Negative(a.y) + e.z.Negative(a.z) + return e +} + +func (e *gfP6) Frobenius(a *gfP6, pool *bnPool) *gfP6 { + e.x.Conjugate(a.x) + e.y.Conjugate(a.y) + e.z.Conjugate(a.z) + + e.x.Mul(e.x, xiTo2PMinus2Over3, pool) + e.y.Mul(e.y, xiToPMinus1Over3, pool) + return e +} + +// FrobeniusP2 computes (xτ²+yτ+z)^(p²) = xτ^(2p²) + yτ^(p²) + z +func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { + // τ^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + e.x.MulScalar(a.x, xiTo2PSquaredMinus2Over3) + // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) + e.y.MulScalar(a.y, xiToPSquaredMinus1Over3) + e.z.Set(a.z) + return e +} + +func (e *gfP6) Add(a, b *gfP6) *gfP6 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + e.z.Add(a.z, b.z) + return e +} + +func (e *gfP6) Sub(a, b *gfP6) *gfP6 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + e.z.Sub(a.z, b.z) + return e +} + +func (e *gfP6) Double(a *gfP6) *gfP6 { + e.x.Double(a.x) + e.y.Double(a.y) + e.z.Double(a.z) + return e +} + +func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + + v0 := newGFp2(pool) + v0.Mul(a.z, b.z, pool) + v1 := newGFp2(pool) + v1.Mul(a.y, b.y, pool) + v2 := newGFp2(pool) + v2.Mul(a.x, b.x, pool) + + t0 := newGFp2(pool) + t0.Add(a.x, a.y) + t1 := newGFp2(pool) + t1.Add(b.x, b.y) + tz := newGFp2(pool) + tz.Mul(t0, t1, pool) + + tz.Sub(tz, v1) + tz.Sub(tz, v2) + tz.MulXi(tz, pool) + tz.Add(tz, v0) + + t0.Add(a.y, a.z) + t1.Add(b.y, b.z) + ty := newGFp2(pool) + ty.Mul(t0, t1, pool) + ty.Sub(ty, v0) + ty.Sub(ty, v1) + t0.MulXi(v2, pool) + ty.Add(ty, t0) + + t0.Add(a.x, a.z) + t1.Add(b.x, b.z) + tx := newGFp2(pool) + tx.Mul(t0, t1, pool) + tx.Sub(tx, v0) + tx.Add(tx, v1) + tx.Sub(tx, v2) + + e.x.Set(tx) + e.y.Set(ty) + e.z.Set(tz) + + t0.Put(pool) + t1.Put(pool) + tx.Put(pool) + ty.Put(pool) + tz.Put(pool) + v0.Put(pool) + v1.Put(pool) + v2.Put(pool) + return e +} + +func (e *gfP6) MulScalar(a *gfP6, b *gfP2, pool *bnPool) *gfP6 { + e.x.Mul(a.x, b, pool) + e.y.Mul(a.y, b, pool) + e.z.Mul(a.z, b, pool) + return e +} + +func (e *gfP6) MulGFP(a *gfP6, b *big.Int) *gfP6 { + e.x.MulScalar(a.x, b) + e.y.MulScalar(a.y, b) + e.z.MulScalar(a.z, b) + return e +} + +// MulTau computes τ·(aτ²+bτ+c) = bτ²+cτ+aξ +func (e *gfP6) MulTau(a *gfP6, pool *bnPool) { + tz := newGFp2(pool) + tz.MulXi(a.x, pool) + ty := newGFp2(pool) + ty.Set(a.y) + e.y.Set(a.z) + e.x.Set(ty) + e.z.Set(tz) + tz.Put(pool) + ty.Put(pool) +} + +func (e *gfP6) Square(a *gfP6, pool *bnPool) *gfP6 { + v0 := newGFp2(pool).Square(a.z, pool) + v1 := newGFp2(pool).Square(a.y, pool) + v2 := newGFp2(pool).Square(a.x, pool) + + c0 := newGFp2(pool).Add(a.x, a.y) + c0.Square(c0, pool) + c0.Sub(c0, v1) + c0.Sub(c0, v2) + c0.MulXi(c0, pool) + c0.Add(c0, v0) + + c1 := newGFp2(pool).Add(a.y, a.z) + c1.Square(c1, pool) + c1.Sub(c1, v0) + c1.Sub(c1, v1) + xiV2 := newGFp2(pool).MulXi(v2, pool) + c1.Add(c1, xiV2) + + c2 := newGFp2(pool).Add(a.x, a.z) + c2.Square(c2, pool) + c2.Sub(c2, v0) + c2.Add(c2, v1) + c2.Sub(c2, v2) + + e.x.Set(c2) + e.y.Set(c1) + e.z.Set(c0) + + v0.Put(pool) + v1.Put(pool) + v2.Put(pool) + c0.Put(pool) + c1.Put(pool) + c2.Put(pool) + xiV2.Put(pool) + + return e +} + +func (e *gfP6) Invert(a *gfP6, pool *bnPool) *gfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = (xτ² + yτ + z)(Cτ²+Bτ+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + τ(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + t1 := newGFp2(pool) + + A := newGFp2(pool) + A.Square(a.z, pool) + t1.Mul(a.x, a.y, pool) + t1.MulXi(t1, pool) + A.Sub(A, t1) + + B := newGFp2(pool) + B.Square(a.x, pool) + B.MulXi(B, pool) + t1.Mul(a.y, a.z, pool) + B.Sub(B, t1) + + C_ := newGFp2(pool) + C_.Square(a.y, pool) + t1.Mul(a.x, a.z, pool) + C_.Sub(C_, t1) + + F := newGFp2(pool) + F.Mul(C_, a.y, pool) + F.MulXi(F, pool) + t1.Mul(A, a.z, pool) + F.Add(F, t1) + t1.Mul(B, a.x, pool) + t1.MulXi(t1, pool) + F.Add(F, t1) + + F.Invert(F, pool) + + e.x.Mul(C_, F, pool) + e.y.Mul(B, F, pool) + e.z.Mul(A, F, pool) + + t1.Put(pool) + A.Put(pool) + B.Put(pool) + C_.Put(pool) + F.Put(pool) + + return e +} diff --git a/common/crypto/bn256/google/main_test.go b/common/crypto/bn256/google/main_test.go new file mode 100644 index 0000000..c0c8545 --- /dev/null +++ b/common/crypto/bn256/google/main_test.go @@ -0,0 +1,71 @@ +package bn256 + +import ( + "testing" + + "crypto/rand" +) + +func TestRandomG2Marshal(t *testing.T) { + for i := 0; i < 10; i++ { + n, g2, err := RandomG2(rand.Reader) + if err != nil { + t.Error(err) + continue + } + t.Logf("%v: %x\n", n, g2.Marshal()) + } +} + +func TestPairings(t *testing.T) { + a1 := new(G1).ScalarBaseMult(bigFromBase10("1")) + a2 := new(G1).ScalarBaseMult(bigFromBase10("2")) + a37 := new(G1).ScalarBaseMult(bigFromBase10("37")) + an1 := new(G1).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + b0 := new(G2).ScalarBaseMult(bigFromBase10("0")) + b1 := new(G2).ScalarBaseMult(bigFromBase10("1")) + b2 := new(G2).ScalarBaseMult(bigFromBase10("2")) + b27 := new(G2).ScalarBaseMult(bigFromBase10("27")) + b999 := new(G2).ScalarBaseMult(bigFromBase10("999")) + bn1 := new(G2).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + p1 := Pair(a1, b1) + pn1 := Pair(a1, bn1) + np1 := Pair(an1, b1) + if pn1.String() != np1.String() { + t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") + } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false negative!") + } + p0 := new(GT).Add(p1, pn1) + p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { + t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") + } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + if p0.String() != p0_3.String() { + t.Error("Pairing mismatch: e(a, b) has wrong order") + } + p2 := Pair(a2, b1) + p2_2 := Pair(a1, b2) + p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") + } + if p2.String() != p2_3.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") + } + if p2.String() == p1.String() { + t.Error("Pairing is degenerate!") + } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false positive!") + } + p999 := Pair(a37, b27) + p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { + t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") + } +} diff --git a/common/crypto/bn256/google/optate.go b/common/crypto/bn256/google/optate.go new file mode 100644 index 0000000..9d69570 --- /dev/null +++ b/common/crypto/bn256/google/optate.go @@ -0,0 +1,397 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { + // See the mixed addition algorithm from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + + B := newGFp2(pool).Mul(p.x, r.t, pool) + + D := newGFp2(pool).Add(p.y, r.z) + D.Square(D, pool) + D.Sub(D, r2) + D.Sub(D, r.t) + D.Mul(D, r.t, pool) + + H := newGFp2(pool).Sub(B, r.x) + I := newGFp2(pool).Square(H, pool) + + E := newGFp2(pool).Add(I, I) + E.Add(E, E) + + J := newGFp2(pool).Mul(H, E, pool) + + L1 := newGFp2(pool).Sub(D, r.y) + L1.Sub(L1, r.y) + + V := newGFp2(pool).Mul(r.x, E, pool) + + rOut = newTwistPoint(pool) + rOut.x.Square(L1, pool) + rOut.x.Sub(rOut.x, J) + rOut.x.Sub(rOut.x, V) + rOut.x.Sub(rOut.x, V) + + rOut.z.Add(r.z, H) + rOut.z.Square(rOut.z, pool) + rOut.z.Sub(rOut.z, r.t) + rOut.z.Sub(rOut.z, I) + + t := newGFp2(pool).Sub(V, rOut.x) + t.Mul(t, L1, pool) + t2 := newGFp2(pool).Mul(r.y, J, pool) + t2.Add(t2, t2) + rOut.y.Sub(t, t2) + + rOut.t.Square(rOut.z, pool) + + t.Add(p.y, rOut.z) + t.Square(t, pool) + t.Sub(t, r2) + t.Sub(t, rOut.t) + + t2.Mul(L1, p.x, pool) + t2.Add(t2, t2) + a = newGFp2(pool) + a.Sub(t2, t) + + c = newGFp2(pool) + c.MulScalar(rOut.z, q.y) + c.Add(c, c) + + b = newGFp2(pool) + b.SetZero() + b.Sub(b, L1) + b.MulScalar(b, q.x) + b.Add(b, b) + + B.Put(pool) + D.Put(pool) + H.Put(pool) + I.Put(pool) + E.Put(pool) + J.Put(pool) + L1.Put(pool) + V.Put(pool) + t.Put(pool) + t2.Put(pool) + + return +} + +func lineFunctionDouble(r *twistPoint, q *curvePoint, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { + // See the doubling algorithm for a=0 from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + + A := newGFp2(pool).Square(r.x, pool) + B := newGFp2(pool).Square(r.y, pool) + C_ := newGFp2(pool).Square(B, pool) + + D := newGFp2(pool).Add(r.x, B) + D.Square(D, pool) + D.Sub(D, A) + D.Sub(D, C_) + D.Add(D, D) + + E := newGFp2(pool).Add(A, A) + E.Add(E, A) + + G := newGFp2(pool).Square(E, pool) + + rOut = newTwistPoint(pool) + rOut.x.Sub(G, D) + rOut.x.Sub(rOut.x, D) + + rOut.z.Add(r.y, r.z) + rOut.z.Square(rOut.z, pool) + rOut.z.Sub(rOut.z, B) + rOut.z.Sub(rOut.z, r.t) + + rOut.y.Sub(D, rOut.x) + rOut.y.Mul(rOut.y, E, pool) + t := newGFp2(pool).Add(C_, C_) + t.Add(t, t) + t.Add(t, t) + rOut.y.Sub(rOut.y, t) + + rOut.t.Square(rOut.z, pool) + + t.Mul(E, r.t, pool) + t.Add(t, t) + b = newGFp2(pool) + b.SetZero() + b.Sub(b, t) + b.MulScalar(b, q.x) + + a = newGFp2(pool) + a.Add(r.x, E) + a.Square(a, pool) + a.Sub(a, A) + a.Sub(a, G) + t.Add(B, B) + t.Add(t, t) + a.Sub(a, t) + + c = newGFp2(pool) + c.Mul(rOut.z, r.t, pool) + c.Add(c, c) + c.MulScalar(c, q.y) + + A.Put(pool) + B.Put(pool) + C_.Put(pool) + D.Put(pool) + E.Put(pool) + G.Put(pool) + t.Put(pool) + + return +} + +func mulLine(ret *gfP12, a, b, c *gfP2, pool *bnPool) { + a2 := newGFp6(pool) + a2.x.SetZero() + a2.y.Set(a) + a2.z.Set(b) + a2.Mul(a2, ret.x, pool) + t3 := newGFp6(pool).MulScalar(ret.y, c, pool) + + t := newGFp2(pool) + t.Add(b, c) + t2 := newGFp6(pool) + t2.x.SetZero() + t2.y.Set(a) + t2.z.Set(t) + ret.x.Add(ret.x, ret.y) + + ret.y.Set(t3) + + ret.x.Mul(ret.x, t2, pool) + ret.x.Sub(ret.x, a2) + ret.x.Sub(ret.x, ret.y) + a2.MulTau(a2, pool) + ret.y.Add(ret.y, a2) + + a2.Put(pool) + t3.Put(pool) + t2.Put(pool) + t.Put(pool) +} + +// sixuPlus2NAF is 6u+2 in non-adjacent form. +var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1} + +// miller implements the Miller loop for calculating the Optimal Ate pairing. +// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf +func miller(q *twistPoint, p *curvePoint, pool *bnPool) *gfP12 { + ret := newGFp12(pool) + ret.SetOne() + + aAffine := newTwistPoint(pool) + aAffine.Set(q) + aAffine.MakeAffine(pool) + + bAffine := newCurvePoint(pool) + bAffine.Set(p) + bAffine.MakeAffine(pool) + + minusA := newTwistPoint(pool) + minusA.Negative(aAffine, pool) + + r := newTwistPoint(pool) + r.Set(aAffine) + + r2 := newGFp2(pool) + r2.Square(aAffine.y, pool) + + for i := len(sixuPlus2NAF) - 1; i > 0; i-- { + a, b, c, newR := lineFunctionDouble(r, bAffine, pool) + if i != len(sixuPlus2NAF)-1 { + ret.Square(ret, pool) + } + + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + switch sixuPlus2NAF[i-1] { + case 1: + a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2, pool) + case -1: + a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2, pool) + default: + continue + } + + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + } + + // In order to calculate Q1 we have to convert q from the sextic twist + // to the full GF(p^12) group, apply the Frobenius there, and convert + // back. + // + // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just + // x for a moment, then after applying the Frobenius, we have x̄ω^(2p) + // where x̄ is the conjugate of x. If we are going to apply the inverse + // isomorphism we need a value with a single coefficient of ω² so we + // rewrite this as x̄ω^(2p-2)ω². ξ⁶ = ω and, due to the construction of + // p, 2p-2 is a multiple of six. Therefore we can rewrite as + // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the + // ω². + // + // A similar argument can be made for the y value. + + q1 := newTwistPoint(pool) + q1.x.Conjugate(aAffine.x) + q1.x.Mul(q1.x, xiToPMinus1Over3, pool) + q1.y.Conjugate(aAffine.y) + q1.y.Mul(q1.y, xiToPMinus1Over2, pool) + q1.z.SetOne() + q1.t.SetOne() + + // For Q2 we are applying the p² Frobenius. The two conjugations cancel + // out and we are left only with the factors from the isomorphism. In + // the case of x, we end up with a pure number which is why + // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We + // ignore this to end up with -Q2. + + minusQ2 := newTwistPoint(pool) + minusQ2.x.MulScalar(aAffine.x, xiToPSquaredMinus1Over3) + minusQ2.y.Set(aAffine.y) + minusQ2.z.SetOne() + minusQ2.t.SetOne() + + r2.Square(q1.y, pool) + a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2, pool) + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + r2.Square(minusQ2.y, pool) + a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2, pool) + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + aAffine.Put(pool) + bAffine.Put(pool) + minusA.Put(pool) + r.Put(pool) + r2.Put(pool) + + return ret +} + +// finalExponentiation computes the (p¹²-1)/Order-th power of an element of +// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from +// http://cryptojedi.org/papers/dclxvi-20100714.pdf) +func finalExponentiation(in *gfP12, pool *bnPool) *gfP12 { + t1 := newGFp12(pool) + + // This is the p^6-Frobenius + t1.x.Negative(in.x) + t1.y.Set(in.y) + + inv := newGFp12(pool) + inv.Invert(in, pool) + t1.Mul(t1, inv, pool) + + t2 := newGFp12(pool).FrobeniusP2(t1, pool) + t1.Mul(t1, t2, pool) + + fp := newGFp12(pool).Frobenius(t1, pool) + fp2 := newGFp12(pool).FrobeniusP2(t1, pool) + fp3 := newGFp12(pool).Frobenius(fp2, pool) + + fu, fu2, fu3 := newGFp12(pool), newGFp12(pool), newGFp12(pool) + fu.Exp(t1, u, pool) + fu2.Exp(fu, u, pool) + fu3.Exp(fu2, u, pool) + + y3 := newGFp12(pool).Frobenius(fu, pool) + fu2p := newGFp12(pool).Frobenius(fu2, pool) + fu3p := newGFp12(pool).Frobenius(fu3, pool) + y2 := newGFp12(pool).FrobeniusP2(fu2, pool) + + y0 := newGFp12(pool) + y0.Mul(fp, fp2, pool) + y0.Mul(y0, fp3, pool) + + y1, y4, y5 := newGFp12(pool), newGFp12(pool), newGFp12(pool) + y1.Conjugate(t1) + y5.Conjugate(fu2) + y3.Conjugate(y3) + y4.Mul(fu, fu2p, pool) + y4.Conjugate(y4) + + y6 := newGFp12(pool) + y6.Mul(fu3, fu3p, pool) + y6.Conjugate(y6) + + t0 := newGFp12(pool) + t0.Square(y6, pool) + t0.Mul(t0, y4, pool) + t0.Mul(t0, y5, pool) + t1.Mul(y3, y5, pool) + t1.Mul(t1, t0, pool) + t0.Mul(t0, y2, pool) + t1.Square(t1, pool) + t1.Mul(t1, t0, pool) + t1.Square(t1, pool) + t0.Mul(t1, y1, pool) + t1.Mul(t1, y0, pool) + t0.Square(t0, pool) + t0.Mul(t0, t1, pool) + + inv.Put(pool) + t1.Put(pool) + t2.Put(pool) + fp.Put(pool) + fp2.Put(pool) + fp3.Put(pool) + fu.Put(pool) + fu2.Put(pool) + fu3.Put(pool) + fu2p.Put(pool) + fu3p.Put(pool) + y0.Put(pool) + y1.Put(pool) + y2.Put(pool) + y3.Put(pool) + y4.Put(pool) + y5.Put(pool) + y6.Put(pool) + + return t0 +} + +func optimalAte(a *twistPoint, b *curvePoint, pool *bnPool) *gfP12 { + e := miller(a, b, pool) + ret := finalExponentiation(e, pool) + e.Put(pool) + + if a.IsInfinity() || b.IsInfinity() { + ret.SetOne() + } + return ret +} diff --git a/common/crypto/bn256/google/twist.go b/common/crypto/bn256/google/twist.go new file mode 100644 index 0000000..43364ff --- /dev/null +++ b/common/crypto/bn256/google/twist.go @@ -0,0 +1,263 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are +// kept in Jacobian form and t=z² when valid. The group G₂ is the set of +// n-torsion points of this curve over GF(p²) (where n = Order) +type twistPoint struct { + x, y, z, t *gfP2 +} + +var twistB = &gfP2{ + bigFromBase10("266929791119991161246907387137283842545076965332900288569378510910307636690"), + bigFromBase10("19485874751759354771024239261021720505790618469301721065564631296452457478373"), +} + +// twistGen is the generator of group G₂. +var twistGen = &twistPoint{ + &gfP2{ + bigFromBase10("11559732032986387107991004021392285783925812861821192530917403151452391805634"), + bigFromBase10("10857046999023057135944570762232829481370756359578518086990519993285655852781"), + }, + &gfP2{ + bigFromBase10("4082367875863433681332203403145435568316851327593401208105741076214120093531"), + bigFromBase10("8495653923123431417604973247489272438418190587263600148770280649306958101930"), + }, + &gfP2{ + bigFromBase10("0"), + bigFromBase10("1"), + }, + &gfP2{ + bigFromBase10("0"), + bigFromBase10("1"), + }, +} + +func newTwistPoint(pool *bnPool) *twistPoint { + return &twistPoint{ + newGFp2(pool), + newGFp2(pool), + newGFp2(pool), + newGFp2(pool), + } +} + +func (c *twistPoint) String() string { + return "(" + c.x.String() + ", " + c.y.String() + ", " + c.z.String() + ")" +} + +func (c *twistPoint) Put(pool *bnPool) { + c.x.Put(pool) + c.y.Put(pool) + c.z.Put(pool) + c.t.Put(pool) +} + +func (c *twistPoint) Set(a *twistPoint) { + c.x.Set(a.x) + c.y.Set(a.y) + c.z.Set(a.z) + c.t.Set(a.t) +} + +// IsOnCurve returns true iff c is on the curve where c must be in affine form. +func (c *twistPoint) IsOnCurve() bool { + pool := new(bnPool) + yy := newGFp2(pool).Square(c.y, pool) + xxx := newGFp2(pool).Square(c.x, pool) + xxx.Mul(xxx, c.x, pool) + yy.Sub(yy, xxx) + yy.Sub(yy, twistB) + yy.Minimal() + + if yy.x.Sign() != 0 || yy.y.Sign() != 0 { + return false + } + cneg := newTwistPoint(pool) + cneg.Mul(c, Order, pool) + return cneg.z.IsZero() +} + +func (c *twistPoint) SetInfinity() { + c.z.SetZero() +} + +func (c *twistPoint) IsInfinity() bool { + return c.z.IsZero() +} + +func (c *twistPoint) Add(a, b *twistPoint, pool *bnPool) { + // For additional comments, see the same function in curve.go. + + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + z1z1 := newGFp2(pool).Square(a.z, pool) + z2z2 := newGFp2(pool).Square(b.z, pool) + u1 := newGFp2(pool).Mul(a.x, z2z2, pool) + u2 := newGFp2(pool).Mul(b.x, z1z1, pool) + + t := newGFp2(pool).Mul(b.z, z2z2, pool) + s1 := newGFp2(pool).Mul(a.y, t, pool) + + t.Mul(a.z, z1z1, pool) + s2 := newGFp2(pool).Mul(b.y, t, pool) + + h := newGFp2(pool).Sub(u2, u1) + xEqual := h.IsZero() + + t.Add(h, h) + i := newGFp2(pool).Square(t, pool) + j := newGFp2(pool).Mul(h, i, pool) + + t.Sub(s2, s1) + yEqual := t.IsZero() + if xEqual && yEqual { + c.Double(a, pool) + return + } + r := newGFp2(pool).Add(t, t) + + v := newGFp2(pool).Mul(u1, i, pool) + + t4 := newGFp2(pool).Square(r, pool) + t.Add(v, v) + t6 := newGFp2(pool).Sub(t4, j) + c.x.Sub(t6, t) + + t.Sub(v, c.x) // t7 + t4.Mul(s1, j, pool) // t8 + t6.Add(t4, t4) // t9 + t4.Mul(r, t, pool) // t10 + c.y.Sub(t4, t6) + + t.Add(a.z, b.z) // t11 + t4.Square(t, pool) // t12 + t.Sub(t4, z1z1) // t13 + t4.Sub(t, z2z2) // t14 + c.z.Mul(t4, h, pool) + + z1z1.Put(pool) + z2z2.Put(pool) + u1.Put(pool) + u2.Put(pool) + t.Put(pool) + s1.Put(pool) + s2.Put(pool) + h.Put(pool) + i.Put(pool) + j.Put(pool) + r.Put(pool) + v.Put(pool) + t4.Put(pool) + t6.Put(pool) +} + +func (c *twistPoint) Double(a *twistPoint, pool *bnPool) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := newGFp2(pool).Square(a.x, pool) + B := newGFp2(pool).Square(a.y, pool) + C_ := newGFp2(pool).Square(B, pool) + + t := newGFp2(pool).Add(a.x, B) + t2 := newGFp2(pool).Square(t, pool) + t.Sub(t2, A) + t2.Sub(t, C_) + d := newGFp2(pool).Add(t2, t2) + t.Add(A, A) + e := newGFp2(pool).Add(t, A) + f := newGFp2(pool).Square(e, pool) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C_, C_) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, c.x) + t2.Mul(e, c.y, pool) + c.y.Sub(t2, t) + + t.Mul(a.y, a.z, pool) + c.z.Add(t, t) + + A.Put(pool) + B.Put(pool) + C_.Put(pool) + t.Put(pool) + t2.Put(pool) + d.Put(pool) + e.Put(pool) + f.Put(pool) +} + +func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoint { + sum := newTwistPoint(pool) + sum.SetInfinity() + t := newTwistPoint(pool) + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum, pool) + if scalar.Bit(i) != 0 { + sum.Add(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + sum.Put(pool) + t.Put(pool) + return c +} + +// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets +// c to 0 : 1 : 0. +func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint { + if c.z.IsOne() { + return c + } + if c.IsInfinity() { + c.x.SetZero() + c.y.SetOne() + c.z.SetZero() + c.t.SetZero() + return c + } + zInv := newGFp2(pool).Invert(c.z, pool) + t := newGFp2(pool).Mul(c.y, zInv, pool) + zInv2 := newGFp2(pool).Square(zInv, pool) + c.y.Mul(t, zInv2, pool) + t.Mul(c.x, zInv2, pool) + c.x.Set(t) + c.z.SetOne() + c.t.SetOne() + + zInv.Put(pool) + t.Put(pool) + zInv2.Put(pool) + + return c +} + +func (c *twistPoint) Negative(a *twistPoint, pool *bnPool) { + c.x.Set(a.x) + c.y.SetZero() + c.y.Sub(c.y, a.y) + c.z.Set(a.z) + c.t.SetZero() +} diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go new file mode 100644 index 0000000..c059006 --- /dev/null +++ b/common/crypto/crypto.go @@ -0,0 +1,338 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package crypto + +import ( + "bufio" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/common/u256" + "math/big" + + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/math" + "github.com/astranetworld/ast/internal/avm/rlp" + "hash" + "io" + "os" + + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +// SignatureLength indicates the byte length required to carry a signature with recovery id. +const SignatureLength = 64 + 1 // 64 bytes ECDSA signature + 1 byte recovery id + +// RecoveryIDOffset points to the byte offset within the signature that contains the recovery id. +const RecoveryIDOffset = 64 + +// DigestLength sets the signature digest exact length +const DigestLength = 32 + +var ( + secp256k1N = new(uint256.Int).SetBytes(hexutil.MustDecode("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")) + secp256k1NBig = secp256k1N.ToBig() + secp256k1halfN = new(uint256.Int).Div(secp256k1N, u256.Num2) + EmptyCodeHash = Keccak256Hash(nil) +) + +var errInvalidPubkey = errors.New("invalid secp256k1 public key") + +// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type KeccakState interface { + hash.Hash + Read([]byte) (int, error) +} + +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// HashData hashes the provided data using the KeccakState and returns a 32 byte hash +func HashData(kh KeccakState, data []byte) (h types.Hash) { + kh.Reset() + //nolint:errcheck + kh.Write(data) + //nolint:errcheck + kh.Read(h[:]) + return h +} + +// Keccak256 calculates and returns the Keccak256 hash of the input data. +func Keccak256(data ...[]byte) []byte { + b := make([]byte, 32) + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(b) //nolint:errcheck + return b +} + +// Keccak256Hash calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256Hash(data ...[]byte) (h types.Hash) { + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(h[:]) //nolint:errcheck + return h +} + +// Keccak512 calculates and returns the Keccak512 hash of the input data. +func Keccak512(data ...[]byte) []byte { + d := sha3.NewLegacyKeccak512() + for _, b := range data { + d.Write(b) + } + return d.Sum(nil) +} + +// CreateAddress creates an ethereum address given the bytes and the nonce +// DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account +func CreateAddress(b types.Address, nonce uint64) types.Address { + data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) + return types.BytesToAddress(Keccak256(data)[12:]) +} + +// CreateAddress2 creates an ethereum address given the address bytes, initial +// contract code hash and a salt. +// DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account +func CreateAddress2(b types.Address, salt [32]byte, inithash []byte) types.Address { + return types.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:]) +} + +// ToECDSA creates a private key with the given D value. +func ToECDSA(d []byte) (*ecdsa.PrivateKey, error) { + return toECDSA(d, true) +} + +// ToECDSAUnsafe blindly converts a binary blob to a private key. It should almost +// never be used unless you are sure the input is valid and want to avoid hitting +// errors due to bad origin encoding (0 prefixes cut off). +func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey { + priv, _ := toECDSA(d, false) + return priv +} + +// toECDSA creates a private key with the given D value. The strict parameter +// controls whether the key's length should be enforced at the curve size or +// it can also accept legacy encodings (0 prefixes). +func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) { + priv := new(ecdsa.PrivateKey) + priv.PublicKey.Curve = S256() + if strict && 8*len(d) != priv.Params().BitSize { + return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize) + } + priv.D = new(big.Int).SetBytes(d) + + // The priv.D must < N + if priv.D.Cmp(secp256k1NBig) >= 0 { + return nil, fmt.Errorf("invalid private key, >=N") + } + // The priv.D must not be zero or negative. + if priv.D.Sign() <= 0 { + return nil, fmt.Errorf("invalid private key, zero or negative") + } + + priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d) + if priv.PublicKey.X == nil { + return nil, errors.New("invalid private key") + } + return priv, nil +} + +// FromECDSA exports a private key into a binary dump. +func FromECDSA(priv *ecdsa.PrivateKey) []byte { + if priv == nil { + return nil + } + return math.PaddedBigBytes(priv.D, priv.Params().BitSize/8) +} + +// UnmarshalPubkey converts bytes to a secp256k1 public key. +func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) { + x, y := elliptic.Unmarshal(S256(), pub) + if x == nil { + return nil, errInvalidPubkey + } + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil +} + +func FromECDSAPub(pub *ecdsa.PublicKey) []byte { + if pub == nil || pub.X == nil || pub.Y == nil { + return nil + } + return elliptic.Marshal(S256(), pub.X, pub.Y) +} + +// UnmarshalPubkeyStd parses a public key from the given bytes in the standard "uncompressed" format. +// The input slice must be 65 bytes long and have this format: [4, X..., Y...] +// See MarshalPubkeyStd. +func UnmarshalPubkeyStd(pub []byte) (*ecdsa.PublicKey, error) { + x, y := elliptic.Unmarshal(S256(), pub) + if x == nil { + return nil, errInvalidPubkey + } + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil +} + +// MarshalPubkeyStd converts a public key into the standard "uncompressed" format. +// It returns a 65 bytes long slice that contains: [4, X..., Y...] +// Returns nil if the given public key is not initialized. +// See UnmarshalPubkeyStd. +func MarshalPubkeyStd(pub *ecdsa.PublicKey) []byte { + if pub == nil || pub.X == nil || pub.Y == nil { + return nil + } + return elliptic.Marshal(S256(), pub.X, pub.Y) +} + +// UnmarshalPubkey parses a public key from the given bytes in the 64 bytes "uncompressed" format. +// The input slice must be 64 bytes long and have this format: [X..., Y...] +// See MarshalPubkey. +//func UnmarshalPubkey(keyBytes []byte) (*ecdsa.PublicKey, error) { +// keyBytes = append([]byte{0x4}, keyBytes...) +// return UnmarshalPubkeyStd(keyBytes) +//} + +// MarshalPubkey converts a public key into a 64 bytes "uncompressed" format. +// It returns a 64 bytes long slice that contains: [X..., Y...] +// In the standard 65 bytes format the first byte is always constant (equal to 4), +// so it can be cut off and trivially recovered later. +// Returns nil if the given public key is not initialized. +// See UnmarshalPubkey. +func MarshalPubkey(pubkey *ecdsa.PublicKey) []byte { + keyBytes := MarshalPubkeyStd(pubkey) + if keyBytes == nil { + return nil + } + return keyBytes[1:] +} + +// HexToECDSA parses a secp256k1 private key. +func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) { + b, err := hex.DecodeString(hexkey) + if byteErr, ok := err.(hex.InvalidByteError); ok { + return nil, fmt.Errorf("invalid hex character %q in private key", byte(byteErr)) + } else if err != nil { + return nil, errors.New("invalid hex data for private key") + } + return ToECDSA(b) +} + +// LoadECDSA loads a secp256k1 private key from the given file. +func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { + fd, err := os.Open(file) + if err != nil { + return nil, err + } + defer fd.Close() + + r := bufio.NewReader(fd) + buf := make([]byte, 64) + n, err := readASCII(buf, r) + if err != nil { + return nil, err + } else if n != len(buf) { + return nil, fmt.Errorf("key file too short, want 64 hex characters") + } + if err := checkKeyFileEnd(r); err != nil { + return nil, err + } + + return HexToECDSA(string(buf)) +} + +// readASCII reads into 'buf', stopping when the buffer is full or +// when a non-printable control character is encountered. +func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { + for ; n < len(buf); n++ { + buf[n], err = r.ReadByte() + switch { + case errors.Is(err, io.EOF) || buf[n] < '!': + return n, nil + case err != nil: + return n, err + } + } + return n, nil +} + +// checkKeyFileEnd skips over additional newlines at the end of a key file. +func checkKeyFileEnd(r *bufio.Reader) error { + for i := 0; ; i++ { + b, err := r.ReadByte() + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: + return err + case b != '\n' && b != '\r': + return fmt.Errorf("invalid character %q at end of key file", b) + case i >= 2: + return errors.New("key file too long, want 64 hex characters") + } + } +} + +// SaveECDSA saves a secp256k1 private key to the given file with +// restrictive permissions. The key data is saved hex-encoded. +func SaveECDSA(file string, key *ecdsa.PrivateKey) error { + k := hex.EncodeToString(FromECDSA(key)) + return os.WriteFile(file, []byte(k), 0600) +} + +// GenerateKey generates a new private key. +func GenerateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(S256(), rand.Reader) +} + +// ValidateSignatureValues verifies whether the signature values are valid with +// the given chain rules. The v value is assumed to be either 0 or 1. +func ValidateSignatureValues(v byte, r, s *uint256.Int, homestead bool) bool { + if r.IsZero() || s.IsZero() { + return false + } + // reject upper range of s values (ECDSA malleability) + // see discussion in secp256k1/libsecp256k1/include/secp256k1.h + if homestead && s.Gt(secp256k1halfN) { + return false + } + // Frontier: allow s to be in full N range + return r.Lt(secp256k1N) && s.Lt(secp256k1N) && (v == 0 || v == 1) +} + +// DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account +func PubkeyToAddress(p ecdsa.PublicKey) types.Address { + pubBytes := MarshalPubkey(&p) + return types.BytesToAddress(Keccak256(pubBytes)[12:]) +} + +func zeroBytes(bytes []byte) { + for i := range bytes { + bytes[i] = 0 + } +} diff --git a/common/crypto/cryptopool/pool.go b/common/crypto/cryptopool/pool.go new file mode 100644 index 0000000..d2faa8e --- /dev/null +++ b/common/crypto/cryptopool/pool.go @@ -0,0 +1,38 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package cryptopool + +import ( + "hash" + "sync" + + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak hashers. +var hasherPool = sync.Pool{ + New: func() interface{} { + return sha3.NewLegacyKeccak256() + }, +} + +func NewLegacyKeccak256() hash.Hash { + h := hasherPool.Get().(hash.Hash) + h.Reset() + return h +} +func ReturnToPoolKeccak256(h hash.Hash) { hasherPool.Put(h) } diff --git a/common/crypto/ecies/.gitignore b/common/crypto/ecies/.gitignore new file mode 100644 index 0000000..802b674 --- /dev/null +++ b/common/crypto/ecies/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +*~ diff --git a/common/crypto/ecies/LICENSE b/common/crypto/ecies/LICENSE new file mode 100644 index 0000000..e1ed19a --- /dev/null +++ b/common/crypto/ecies/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013 Kyle Isom +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/crypto/ecies/README b/common/crypto/ecies/README new file mode 100644 index 0000000..2650c7b --- /dev/null +++ b/common/crypto/ecies/README @@ -0,0 +1,94 @@ +# NOTE + +This implementation is direct fork of Kylom's implementation. I claim no authorship over this code apart from some minor modifications. +Please be aware this code **has not yet been reviewed**. + +ecies implements the Elliptic Curve Integrated Encryption Scheme. + +The package is designed to be compliant with the appropriate NIST +standards, and therefore doesn't support the full SEC 1 algorithm set. + + +STATUS: + +ecies should be ready for use. The ASN.1 support is only complete so +far as to supported the listed algorithms before. + + +CAVEATS + +1. CMAC support is currently not present. + + +SUPPORTED ALGORITHMS + + SYMMETRIC CIPHERS HASH FUNCTIONS + AES128 SHA-1 + AES192 SHA-224 + AES256 SHA-256 + SHA-384 + ELLIPTIC CURVE SHA-512 + P256 + P384 KEY DERIVATION FUNCTION + P521 NIST SP 800-65a Concatenation KDF + +Curve P224 isn't supported because it does not provide a minimum security +level of AES128 with HMAC-SHA1. According to NIST SP 800-57, the security +level of P224 is 112 bits of security. Symmetric ciphers use CTR-mode; +message tags are computed using HMAC- function. + + +CURVE SELECTION + +According to NIST SP 800-57, the following curves should be selected: + + +----------------+-------+ + | SYMMETRIC SIZE | CURVE | + +----------------+-------+ + | 128-bit | P256 | + +----------------+-------+ + | 192-bit | P384 | + +----------------+-------+ + | 256-bit | P521 | + +----------------+-------+ + + +TODO + +1. Look at serialising the parameters with the SEC 1 ASN.1 module. +2. Validate ASN.1 formats with SEC 1. + + +TEST VECTORS + +The only test vectors I've found so far date from 1993, predating AES +and including only 163-bit curves. Therefore, there are no published +test vectors to compare to. + + +LICENSE + +ecies is released under the same license as the Go source code. See the +LICENSE file for details. + + +REFERENCES + +* SEC (Standard for Efficient Cryptography) 1, version 2.0: Elliptic + Curve Cryptography; Certicom, May 2009. + http://www.secg.org/sec1-v2.pdf +* GEC (Guidelines for Efficient Cryptography) 2, version 0.3: Test + Vectors for SEC 1; Certicom, September 1999. + http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf +* NIST SP 800-56a: Recommendation for Pair-Wise Key Establishment Schemes + Using Discrete Logarithm Cryptography. National Institute of Standards + and Technology, May 2007. + http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf +* Suite B Implementer’s Guide to NIST SP 800-56A. National Security + Agency, July 28, 2009. + http://www.nsa.gov/ia/_files/SuiteB_Implementer_G-113808.pdf +* NIST SP 800-57: Recommendation for Key Management – Part 1: General + (Revision 3). National Institute of Standards and Technology, July + 2012. + http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf + diff --git a/common/crypto/ecies/ecies.go b/common/crypto/ecies/ecies.go new file mode 100644 index 0000000..64b5a99 --- /dev/null +++ b/common/crypto/ecies/ecies.go @@ -0,0 +1,317 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +import ( + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/subtle" + "encoding/binary" + "fmt" + "hash" + "io" + "math/big" +) + +var ( + ErrImport = fmt.Errorf("ecies: failed to import key") + ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve") + ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key") + ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity") + ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big") +) + +// PublicKey is a representation of an elliptic curve public key. +type PublicKey struct { + X *big.Int + Y *big.Int + elliptic.Curve + Params *ECIESParams +} + +// Export an ECIES public key as an ECDSA public key. +func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey { + return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y} +} + +// Import an ECDSA public key as an ECIES public key. +func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey { + return &PublicKey{ + X: pub.X, + Y: pub.Y, + Curve: pub.Curve, + Params: ParamsFromCurve(pub.Curve), + } +} + +// PrivateKey is a representation of an elliptic curve private key. +type PrivateKey struct { + PublicKey + D *big.Int +} + +// Export an ECIES private key as an ECDSA private key. +func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { + pub := &prv.PublicKey + pubECDSA := pub.ExportECDSA() + return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} +} + +// Import an ECDSA private key as an ECIES private key. +func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey { + pub := ImportECDSAPublic(&prv.PublicKey) + return &PrivateKey{*pub, prv.D} +} + +// Generate an elliptic curve public / private keypair. If params is nil, +// the recommended default parameters for the key will be chosen. +func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) { + pb, x, y, err := elliptic.GenerateKey(curve, rand) + if err != nil { + return + } + prv = new(PrivateKey) + prv.PublicKey.X = x + prv.PublicKey.Y = y + prv.PublicKey.Curve = curve + prv.D = new(big.Int).SetBytes(pb) + if params == nil { + params = ParamsFromCurve(curve) + } + prv.PublicKey.Params = params + return +} + +// MaxSharedKeyLength returns the maximum length of the shared key the +// public key can produce. +func MaxSharedKeyLength(pub *PublicKey) int { + return (pub.Curve.Params().BitSize + 7) / 8 +} + +// ECDH key agreement method used to establish secret keys for encryption. +func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) { + if prv.PublicKey.Curve != pub.Curve { + return nil, ErrInvalidCurve + } + if skLen+macLen > MaxSharedKeyLength(pub) { + return nil, ErrSharedKeyTooBig + } + + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) + if x == nil { + return nil, ErrSharedKeyIsPointAtInfinity + } + + sk = make([]byte, skLen+macLen) + skBytes := x.Bytes() + copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil +} + +var ( + ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") + ErrInvalidMessage = fmt.Errorf("ecies: invalid message") +) + +// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). +func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { + counterBytes := make([]byte, 4) + k := make([]byte, 0, roundup(kdLen, hash.Size())) + for counter := uint32(1); len(k) < kdLen; counter++ { + binary.BigEndian.PutUint32(counterBytes, counter) + hash.Reset() + hash.Write(counterBytes) + hash.Write(z) + hash.Write(s1) + k = hash.Sum(k) + } + return k[:kdLen] +} + +// roundup rounds size up to the next multiple of blocksize. +func roundup(size, blocksize int) int { + return size + blocksize - (size % blocksize) +} + +// deriveKeys creates the encryption and MAC keys using concatKDF. +func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { + K := concatKDF(hash, z, s1, 2*keyLen) + Ke = K[:keyLen] + Km = K[keyLen:] + hash.Reset() + hash.Write(Km) + Km = hash.Sum(Km[:0]) + return Ke, Km +} + +// messageTag computes the MAC of a message (called the tag) as per +// SEC 1, 3.5. +func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { + mac := hmac.New(hash, km) + mac.Write(msg) + mac.Write(shared) + tag := mac.Sum(nil) + return tag +} + +// Generate an initialisation vector for CTR mode. +func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { + iv = make([]byte, params.BlockSize) + _, err = io.ReadFull(rand, iv) + return +} + +// symEncrypt carries out CTR encryption using the block cipher specified in the +func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { + c, err := params.Cipher(key) + if err != nil { + return + } + + iv, err := generateIV(params, rand) + if err != nil { + return + } + ctr := cipher.NewCTR(c, iv) + + ct = make([]byte, len(m)+params.BlockSize) + copy(ct, iv) + ctr.XORKeyStream(ct[params.BlockSize:], m) + return +} + +// symDecrypt carries out CTR decryption using the block cipher specified in +// the parameters +func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { + c, err := params.Cipher(key) + if err != nil { + return + } + + ctr := cipher.NewCTR(c, ct[:params.BlockSize]) + + m = make([]byte, len(ct)-params.BlockSize) + ctr.XORKeyStream(m, ct[params.BlockSize:]) + return +} + +// Encrypt encrypts a message using ECIES as specified in SEC 1, 5.1. +// +// s1 and s2 contain shared information that is not part of the resulting +// ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the +// shared information parameters aren't being used, they should be nil. +func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) { + params, err := pubkeyParams(pub) + if err != nil { + return nil, err + } + + R, err := GenerateKey(rand, pub.Curve, params) + if err != nil { + return nil, err + } + + z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) + if err != nil { + return nil, err + } + + hash := params.Hash() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) + + em, err := symEncrypt(rand, params, Ke, m) + if err != nil || len(em) <= params.BlockSize { + return nil, err + } + + d := messageTag(params.Hash, Km, em, s2) + + Rb := elliptic.Marshal(pub.Curve, R.PublicKey.X, R.PublicKey.Y) + ct = make([]byte, len(Rb)+len(em)+len(d)) + copy(ct, Rb) + copy(ct[len(Rb):], em) + copy(ct[len(Rb)+len(em):], d) + return ct, nil +} + +// Decrypt decrypts an ECIES ciphertext. +func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { + if len(c) == 0 { + return nil, ErrInvalidMessage + } + params, err := pubkeyParams(&prv.PublicKey) + if err != nil { + return nil, err + } + + hash := params.Hash() + + var ( + rLen int + hLen int = hash.Size() + mStart int + mEnd int + ) + + switch c[0] { + case 2, 3, 4: + rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4 + if len(c) < (rLen + hLen + 1) { + return nil, ErrInvalidMessage + } + default: + return nil, ErrInvalidPublicKey + } + + mStart = rLen + mEnd = len(c) - hLen + + R := new(PublicKey) + R.Curve = prv.PublicKey.Curve + R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen]) + if R.X == nil { + return nil, ErrInvalidPublicKey + } + + z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) + if err != nil { + return nil, err + } + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) + + d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) + if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { + return nil, ErrInvalidMessage + } + + return symDecrypt(params, Ke, c[mStart:mEnd]) +} diff --git a/common/crypto/ecies/ecies_test.go b/common/crypto/ecies/ecies_test.go new file mode 100644 index 0000000..dc54ec6 --- /dev/null +++ b/common/crypto/ecies/ecies_test.go @@ -0,0 +1,429 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +import ( + "bytes" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/astranetworld/ast/common/crypto" +) + +func TestKDF(t *testing.T) { + tests := []struct { + length int + output []byte + }{ + {6, decode("858b192fa2ed")}, + {32, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0")}, + {48, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955")}, + {64, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955f3467fd6672cce1024c5b1effccc0f61")}, + } + + for _, test := range tests { + h := sha256.New() + k := concatKDF(h, []byte("input"), nil, test.length) + if !bytes.Equal(k, test.output) { + t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output) + } + } +} + +var ErrBadSharedKeys = fmt.Errorf("ecies: shared keys don't match") + +// cmpParams compares a set of ECIES parameters. We assume, as per the +// docs, that AES is the only supported symmetric encryption algorithm. +func cmpParams(p1, p2 *ECIESParams) bool { + return p1.hashAlgo == p2.hashAlgo && + p1.KeyLen == p2.KeyLen && + p1.BlockSize == p2.BlockSize +} + +// Validate the ECDH component. +func TestSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys) + } +} + +func TestSharedKeyPadding(t *testing.T) { + // sanity checks + prv0 := hexKey("1adf5c18167d96a1f9a0b1ef63be8aa27eaf6032c233b2b38f7850cf5b859fd9") + prv1 := hexKey("0097a076fc7fcd9208240668e31c9abee952cbb6e375d1b8febc7499d6e16f1a") + x0, _ := new(big.Int).SetString("1a8ed022ff7aec59dc1b440446bdda5ff6bcb3509a8b109077282b361efffbd8", 16) + x1, _ := new(big.Int).SetString("6ab3ac374251f638d0abb3ef596d1dc67955b507c104e5f2009724812dc027b8", 16) + y0, _ := new(big.Int).SetString("e040bd480b1deccc3bc40bd5b1fdcb7bfd352500b477cb9471366dbd4493f923", 16) + y1, _ := new(big.Int).SetString("8ad915f2b503a8be6facab6588731fefeb584fd2dfa9a77a5e0bba1ec439e4fa", 16) + + if prv0.PublicKey.X.Cmp(x0) != 0 { + t.Errorf("mismatched prv0.X:\nhave: %x\nwant: %x\n", prv0.PublicKey.X.Bytes(), x0.Bytes()) + } + if prv0.PublicKey.Y.Cmp(y0) != 0 { + t.Errorf("mismatched prv0.Y:\nhave: %x\nwant: %x\n", prv0.PublicKey.Y.Bytes(), y0.Bytes()) + } + if prv1.PublicKey.X.Cmp(x1) != 0 { + t.Errorf("mismatched prv1.X:\nhave: %x\nwant: %x\n", prv1.PublicKey.X.Bytes(), x1.Bytes()) + } + if prv1.PublicKey.Y.Cmp(y1) != 0 { + t.Errorf("mismatched prv1.Y:\nhave: %x\nwant: %x\n", prv1.PublicKey.Y.Bytes(), y1.Bytes()) + } + + // test shared secret generation + sk1, err := prv0.GenerateShared(&prv1.PublicKey, 16, 16) + if err != nil { + t.Log(err.Error()) + } + + sk2, err := prv1.GenerateShared(&prv0.PublicKey, 16, 16) + if err != nil { + t.Fatal(err.Error()) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys.Error()) + } +} + +// Verify that the key generation code fails when too much key data is +// requested. +func TestTooBigSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + _, err = prv1.GenerateShared(&prv2.PublicKey, 32, 32) + if err != ErrSharedKeyTooBig { + t.Fatal("ecdh: shared key should be too large for curve") + } + + _, err = prv2.GenerateShared(&prv1.PublicKey, 32, 32) + if err != ErrSharedKeyTooBig { + t.Fatal("ecdh: shared key should be too large for curve") + } +} + +// Benchmark the generation of P256 keys. +func BenchmarkGenerateKeyP256(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := GenerateKey(rand.Reader, elliptic.P256(), nil); err != nil { + b.Fatal(err) + } + } +} + +// Benchmark the generation of P256 shared keys. +func BenchmarkGenSharedKeyP256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, elliptic.P256(), nil) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) + if err != nil { + b.Fatal(err) + } + } +} + +// Benchmark the generation of S256 shared keys. +func BenchmarkGenSharedKeyS256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, crypto.S256(), nil) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) + if err != nil { + b.Fatal(err) + } + } +} + +// Verify that an encrypted message can be successfully decrypted. +func TestEncryptDecrypt(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + + _, err = prv1.Decrypt(ct, nil, nil) + if err == nil { + t.Fatal("ecies: encryption should not have succeeded") + } +} + +func TestDecryptShared2(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + message := []byte("Hello, world.") + shared2 := []byte("shared data 2") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, shared2) + if err != nil { + t.Fatal(err) + } + + // Check that decrypting with correct shared data works. + pt, err := prv.Decrypt(ct, nil, shared2) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + + // Decrypting without shared data or incorrect shared data fails. + if _, err = prv.Decrypt(ct, nil, nil); err == nil { + t.Fatal("ecies: decrypting without shared data didn't fail") + } + if _, err = prv.Decrypt(ct, nil, []byte("garbage")); err == nil { + t.Fatal("ecies: decrypting with incorrect shared data didn't fail") + } +} + +type testCase struct { + Curve elliptic.Curve + Name string + Expected *ECIESParams +} + +var testCases = []testCase{ + { + Curve: elliptic.P256(), + Name: "P256", + Expected: ECIES_AES128_SHA256, + }, + { + Curve: elliptic.P384(), + Name: "P384", + Expected: ECIES_AES192_SHA384, + }, + { + Curve: elliptic.P521(), + Name: "P521", + Expected: ECIES_AES256_SHA512, + }, +} + +// Test parameter selection for each curve, and that P224 fails automatic +// parameter selection (see README for a discussion of P224). Ensures that +// selecting a set of parameters automatically for the given curve works. +func TestParamSelection(t *testing.T) { + for _, c := range testCases { + testParamSelection(t, c) + } +} + +func testParamSelection(t *testing.T, c testCase) { + params := ParamsFromCurve(c.Curve) + if params == nil { + t.Fatal("ParamsFromCurve returned nil") + } else if params != nil && !cmpParams(params, c.Expected) { + t.Fatalf("ecies: parameters should be invalid (%s)\n", c.Name) + } + + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + if !bytes.Equal(pt, message) { + t.Fatalf("ecies: plaintext doesn't match message (%s)\n", c.Name) + } + + _, err = prv1.Decrypt(ct, nil, nil) + if err == nil { + t.Fatalf("ecies: encryption should not have succeeded (%s)\n", c.Name) + } +} + +// Ensure that the basic public key validation in the decryption operation +// works. +func TestBasicKeyValidation(t *testing.T) { + badBytes := []byte{0, 1, 5, 6, 7, 8, 9} + + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + for _, b := range badBytes { + ct[0] = b + _, err := prv.Decrypt(ct, nil, nil) + if err != ErrInvalidPublicKey { + t.Fatal("ecies: validated an invalid key") + } + } +} + +func TestBox(t *testing.T) { + prv1 := hexKey("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f") + prv2 := hexKey("d0b043b4c5d657670778242d82d68a29d25d7d711127d17b8e299f156dad361a") + pub2 := &prv2.PublicKey + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, pub2, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + if _, err = prv1.Decrypt(ct, nil, nil); err == nil { + t.Fatal("ecies: encryption should not have succeeded") + } +} + +// Verify GenerateShared against static values - useful when +// debugging changes in underlying libs +func TestSharedKeyStatic(t *testing.T) { + prv1 := hexKey("7ebbc6a8358bc76dd73ebc557056702c8cfc34e5cfcd90eb83af0347575fd2ad") + prv2 := hexKey("6a3d6396903245bba5837752b9e0348874e72db0c4e11e9c485a81b4ea4353b9") + + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys) + } + + sk := decode("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") + if !bytes.Equal(sk1, sk) { + t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1) + } +} + +func hexKey(prv string) *PrivateKey { + key, err := crypto.HexToECDSA(prv) + if err != nil { + panic(err) + } + return ImportECDSA(key) +} + +func decode(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} diff --git a/common/crypto/ecies/params.go b/common/crypto/ecies/params.go new file mode 100644 index 0000000..ecc85f5 --- /dev/null +++ b/common/crypto/ecies/params.go @@ -0,0 +1,144 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +// This file contains parameters for ECIES encryption, specifying the +// symmetric encryption and HMAC parameters. + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + "crypto/sha256" + "crypto/sha512" + "fmt" + "hash" + + ethcrypto "github.com/astranetworld/ast/common/crypto" +) + +var ( + DefaultCurve = ethcrypto.S256() + ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") + ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") + ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> %d) in ECIESParams", maxKeyLen) +) + +// KeyLen is limited to prevent overflow of the counter +// in concatKDF. While the theoretical limit is much higher, +// no known cipher uses keys larger than 512 bytes. +const maxKeyLen = 512 + +type ECIESParams struct { + Hash func() hash.Hash // hash function + hashAlgo crypto.Hash + Cipher func([]byte) (cipher.Block, error) // symmetric cipher + BlockSize int // block size of symmetric cipher + KeyLen int // length of symmetric key +} + +// Standard ECIES parameters: +// * ECIES using AES128 and HMAC-SHA-256-16 +// * ECIES using AES256 and HMAC-SHA-256-32 +// * ECIES using AES256 and HMAC-SHA-384-48 +// * ECIES using AES256 and HMAC-SHA-512-64 + +var ( + ECIES_AES128_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 16, + } + + ECIES_AES192_SHA384 = &ECIESParams{ + Hash: sha512.New384, + hashAlgo: crypto.SHA384, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 24, + } + + ECIES_AES256_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA384 = &ECIESParams{ + Hash: sha512.New384, + hashAlgo: crypto.SHA384, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA512 = &ECIESParams{ + Hash: sha512.New, + hashAlgo: crypto.SHA512, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } +) + +var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ + ethcrypto.S256(): ECIES_AES128_SHA256, + elliptic.P256(): ECIES_AES128_SHA256, + elliptic.P384(): ECIES_AES192_SHA384, + elliptic.P521(): ECIES_AES256_SHA512, +} + +func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { + paramsFromCurve[curve] = params +} + +// ParamsFromCurve selects parameters optimal for the selected elliptic curve. +// Only the curves P256, P384, and P512 are supported. +func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { + return paramsFromCurve[curve] +} + +func pubkeyParams(key *PublicKey) (*ECIESParams, error) { + params := key.Params + if params == nil { + if params = ParamsFromCurve(key.Curve); params == nil { + return nil, ErrUnsupportedECIESParameters + } + } + if params.KeyLen > maxKeyLen { + return nil, ErrInvalidKeyLen + } + return params, nil +} diff --git a/common/crypto/rand/BUILD.bazel b/common/crypto/rand/BUILD.bazel new file mode 100644 index 0000000..c8039ec --- /dev/null +++ b/common/crypto/rand/BUILD.bazel @@ -0,0 +1,14 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["rand.go"], + importpath = "github.com/prysmaticlabs/prysm/v3/crypto/rand", + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["rand_test.go"], + embed = [":go_default_library"], +) diff --git a/common/crypto/rand/rand.go b/common/crypto/rand/rand.go new file mode 100644 index 0000000..5e7ecdb --- /dev/null +++ b/common/crypto/rand/rand.go @@ -0,0 +1,86 @@ +/* +Package rand defines methods of obtaining random number generators. + +One is expected to use randomness from this package only, without introducing any other packages. +This limits the scope of code that needs to be hardened. + +There are two modes, one for deterministic and another non-deterministic randomness: +1. If deterministic pseudo-random generator is enough, use: + + import "github.com/prysmaticlabs/prysm/v3/crypto/rand" + randGen := rand.NewDeterministicGenerator() + randGen.Intn(32) // or any other func defined in math.rand API + + In this mode, only seed is generated using cryptographically secure source (crypto/rand). So, + once seed is obtained, and generator is seeded, the next generations are deterministic, thus fast. + However given that we only seed this 63 bits from crypto/rand and use math/rand to generate the outputs, + this method is not cryptographically secure. This is directly stated in the math/rand package, + https://github.com/golang/go/blob/release-branch.go1.17/src/math/rand/rand.go#L15. For any security + sensitive work this particular generator is NOT to be used. + +2. For cryptographically secure non-deterministic mode (CSPRNG), use: + + import "github.com/prysmaticlabs/prysm/v3/crypto/rand" + randGen := rand.NewGenerator() + randGen.Intn(32) // or any other func defined in math.rand API + + Again, any of the functions from `math/rand` can be used, however, they all use custom source + of randomness (crypto/rand), on every step. This makes randomness non-deterministic. However, + you take a performance hit -- as it is an order of magnitude slower. +*/ +package rand + +import ( + "crypto/rand" + "encoding/binary" + mrand "math/rand" + "sync" +) + +type source struct{} + +var lock sync.RWMutex +var _ mrand.Source64 = (*source)(nil) // #nosec G404 -- This ensures we meet the interface + +// Seed does nothing when crypto/rand is used as source. +func (_ *source) Seed(_ int64) {} + +// Int63 returns uniformly-distributed random (as in CSPRNG) int64 value within [0, 1<<63) range. +// Panics if random generator reader cannot return data. +func (s *source) Int63() int64 { + return int64(s.Uint64() & ^uint64(1<<63)) +} + +// Uint64 returns uniformly-distributed random (as in CSPRNG) uint64 value within [0, 1<<64) range. +// Panics if random generator reader cannot return data. +func (_ *source) Uint64() (val uint64) { + lock.RLock() + defer lock.RUnlock() + if err := binary.Read(rand.Reader, binary.BigEndian, &val); err != nil { + panic(err) + } + return +} + +// Rand is alias for underlying random generator. +type Rand = mrand.Rand // #nosec G404 + +// NewGenerator returns a new generator that uses random values from crypto/rand as a source +// (cryptographically secure random number generator). +// Panics if crypto/rand input cannot be read. +// Use it for everything where crypto secure non-deterministic randomness is required. Performance +// takes a hit, so use sparingly. +func NewGenerator() *Rand { + return mrand.New(&source{}) // #nosec G404 -- excluded +} + +// NewDeterministicGenerator returns a random generator which is only seeded with crypto/rand, +// but is deterministic otherwise (given seed, produces given results, deterministically). +// Panics if crypto/rand input cannot be read. +// Use this method for performance, where deterministic pseudo-random behaviour is enough. +// Otherwise, rely on NewGenerator(). This method is not cryptographically secure as outputs +// can be potentially predicted even without knowledge of the underlying seed. +func NewDeterministicGenerator() *Rand { + randGen := NewGenerator() + return mrand.New(mrand.NewSource(randGen.Int63())) // #nosec G404 -- excluded +} diff --git a/common/crypto/rand/rand_test.go b/common/crypto/rand/rand_test.go new file mode 100644 index 0000000..599006d --- /dev/null +++ b/common/crypto/rand/rand_test.go @@ -0,0 +1,24 @@ +package rand + +import ( + "math/rand" + "testing" +) + +func TestNewGenerator(_ *testing.T) { + // Make sure that generation works, no panics. + randGen := NewGenerator() + _ = randGen.Int63() + _ = randGen.Uint64() + _ = randGen.Intn(32) + var _ = rand.Source64(randGen) +} + +func TestNewDeterministicGenerator(_ *testing.T) { + // Make sure that generation works, no panics. + randGen := NewDeterministicGenerator() + _ = randGen.Int63() + _ = randGen.Uint64() + _ = randGen.Intn(32) + var _ = rand.Source64(randGen) +} diff --git a/common/crypto/signature_cgo.go b/common/crypto/signature_cgo.go new file mode 100644 index 0000000..f4c803c --- /dev/null +++ b/common/crypto/signature_cgo.go @@ -0,0 +1,92 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +//go:build !nacl && !js && cgo && !gofuzz + +package crypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/astranetworld/ast/common/math" + "github.com/ledgerwatch/secp256k1" +) + +// Ecrecover returns the uncompressed public key that created the given signature. +func Ecrecover(hash, sig []byte) ([]byte, error) { + return secp256k1.RecoverPubkey(hash, sig) +} + +// Ecrecover returns the uncompressed public key that created the given signature. +func EcrecoverWithContext(context *secp256k1.Context, hash, sig []byte) ([]byte, error) { + return secp256k1.RecoverPubkeyWithContext(context, hash, sig, nil) +} + +// SigToPub returns the public key that created the given signature. +func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { + s, err := Ecrecover(hash, sig) + if err != nil { + return nil, err + } + + x, y := elliptic.Unmarshal(S256(), s) + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil +} + +// Sign calculates an ECDSA signature. +// +// This function is susceptible to chosen plaintext attacks that can leak +// information about the private key that is used for signing. Callers must +// be aware that the given digest cannot be chosen by an adversery. Common +// solution is to hash any input before calculating the signature. +// +// The produced signature is in the [R || S || V] format where V is 0 or 1. +func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { + if len(digestHash) != DigestLength { + return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(digestHash)) + } + seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8) + defer zeroBytes(seckey) + return secp256k1.Sign(digestHash, seckey) +} + +// VerifySignature checks that the given public key created signature over digest. +// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format. +// The signature should have the 64 byte [R || S] format. +func VerifySignature(pubkey, digestHash, signature []byte) bool { + return secp256k1.VerifySignature(pubkey, digestHash, signature) +} + +// DecompressPubkey parses a public key in the 33-byte compressed format. +func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) { + x, y := secp256k1.DecompressPubkey(pubkey) + if x == nil { + return nil, fmt.Errorf("invalid public key") + } + return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil +} + +// CompressPubkey encodes a public key to the 33-byte compressed format. +func CompressPubkey(pubkey *ecdsa.PublicKey) []byte { + return secp256k1.CompressPubkey(pubkey.X, pubkey.Y) +} + +// S256 returns an instance of the secp256k1 curve. +func S256() elliptic.Curve { + return secp256k1.S256() +} diff --git a/common/db/database.go b/common/db/database.go new file mode 100644 index 0000000..4674aa4 --- /dev/null +++ b/common/db/database.go @@ -0,0 +1,73 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package db + +type IDBReader interface { + Get(key []byte) (value []byte, err error) + Gets(key []byte, count uint) (keys [][]byte, value [][]byte, err error) + GetIterator(key []byte) (iterator IIterator, err error) +} + +type IDBWriter interface { + Put(key []byte, value []byte) (err error) + Puts(keys [][]byte, values [][]byte) (err error) + Delete(key []byte) (err error) + Drop() (err error) +} + +/* +IIterator mdbx must close +*/ +type IIterator interface { + Next() (err error) + Prev() (err error) + Key() (key []byte, err error) + Value() (value []byte, err error) + Close() +} + +type IDBReaderWriter interface { + IDBReader + IDBWriter +} + +type IDatabaseReader interface { + IDBReader +} + +type IDatabaseWriter interface { + IDBWriter +} + +type IDatabaseWriterReader interface { + IDBReaderWriter +} + +type IDatabase interface { + OpenReader(dbName string) (reader IDatabaseReader, err error) + OpenWriter(dbName string) (writer IDatabaseWriter, err error) + Open(dbName string) (rw IDatabaseWriterReader, err error) + Snapshot() (ISnapshot, error) + Close() (err error) +} + +type ISnapshot interface { + Put(dbName string, key []byte, value []byte) (err error) + Get(dbName string, key []byte) (value []byte, err error) + Commit() error + Rollback() +} diff --git a/common/events.go b/common/events.go new file mode 100644 index 0000000..97780dc --- /dev/null +++ b/common/events.go @@ -0,0 +1,59 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/modules/state" + "github.com/libp2p/go-libp2p/core/peer" +) + +// NewLocalTxsEvent local txs +type NewLocalTxsEvent struct{ Txs []*transaction.Transaction } + +// NewTxsEvent txs +type NewTxsEvent struct{ Txs []*transaction.Transaction } + +// NewLogsEvent new logs +type NewLogsEvent struct{ Logs []*block.Log } + +// RemovedLogsEvent is posted when a reorg happens // todo blockchain v2 +type RemovedLogsEvent struct{ Logs []*block.Log } + +// NewPendingLogsEvent is posted when a reorg happens // todo miner v2 +type NewPendingLogsEvent struct{ Logs []*block.Log } + +// PeerJoinEvent Peer join +type PeerJoinEvent struct{ Peer peer.ID } + +// PeerDropEvent Peer drop +type PeerDropEvent struct{ Peer peer.ID } + +// DownloaderStartEvent start download +type DownloaderStartEvent struct{} + +// DownloaderFinishEvent finish download +type DownloaderFinishEvent struct{} + +type ChainHighestBlock struct { + Block block.Block + Inserted bool +} +type MinedEntireEvent struct { + Entire state.EntireCode +} diff --git a/common/format.go b/common/format.go new file mode 100644 index 0000000..ef82196 --- /dev/null +++ b/common/format.go @@ -0,0 +1,82 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "fmt" + "regexp" + "strings" + "time" +) + +// PrettyDuration is a pretty printed version of a time.Duration value that cuts +// the unnecessary precision off from the formatted textual representation. +type PrettyDuration time.Duration + +var prettyDurationRe = regexp.MustCompile(`\.[0-9]+`) + +// String implements the Stringer interface, allowing pretty printing of duration +// values rounded to three decimals. +func (d PrettyDuration) String() string { + label := fmt.Sprintf("%v", time.Duration(d)) + if match := prettyDurationRe.FindString(label); len(match) > 4 { + label = strings.Replace(label, match, match[:4], 1) + } + return label +} + +// PrettyAge is a pretty printed version of a time.Duration value that rounds +// the values up to a single most significant unit, days/weeks/years included. +type PrettyAge time.Time + +// ageUnits is a list of units the age pretty printing uses. +var ageUnits = []struct { + Size time.Duration + Symbol string +}{ + {12 * 30 * 24 * time.Hour, "y"}, + {30 * 24 * time.Hour, "mo"}, + {7 * 24 * time.Hour, "w"}, + {24 * time.Hour, "d"}, + {time.Hour, "h"}, + {time.Minute, "m"}, + {time.Second, "s"}, +} + +// String implements the Stringer interface, allowing pretty printing of duration +// values rounded to the most significant time unit. +func (t PrettyAge) String() string { + // Calculate the time difference and handle the 0 cornercase + diff := time.Since(time.Time(t)) + if diff < time.Second { + return "0" + } + // Accumulate a precision of 3 components before returning + result, prec := "", 0 + + for _, unit := range ageUnits { + if diff > unit.Size { + result = fmt.Sprintf("%s%d%s", result, diff/unit.Size, unit.Symbol) + diff %= unit.Size + + if prec += 1; prec >= 3 { + break + } + } + } + return result +} diff --git a/common/gaspool.go b/common/gaspool.go new file mode 100644 index 0000000..f50e494 --- /dev/null +++ b/common/gaspool.go @@ -0,0 +1,55 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "fmt" + "github.com/astranetworld/ast/core" + "math" +) + +// GasPool tracks the amount of gas available during execution of the transactions +// in a block. The zero value is a pool with zero gas available. +type GasPool uint64 + +// AddGas makes gas available for execution. +func (gp *GasPool) AddGas(amount uint64) *GasPool { + if uint64(*gp) > math.MaxUint64-amount { + panic("gas pool pushed above uint64") + } + *(*uint64)(gp) += amount + return gp +} + +// SubGas deducts the given amount from the pool if enough gas is +// available and returns an error otherwise. +func (gp *GasPool) SubGas(amount uint64) error { + if uint64(*gp) < amount { + return core.ErrGasLimitReached + } + *(*uint64)(gp) -= amount + return nil +} + +// Gas returns the amount of gas remaining in the pool. +func (gp *GasPool) Gas() uint64 { + return uint64(*gp) +} + +func (gp *GasPool) String() string { + return fmt.Sprintf("%d", *gp) +} diff --git a/common/hash/hash.go b/common/hash/hash.go new file mode 100644 index 0000000..8005048 --- /dev/null +++ b/common/hash/hash.go @@ -0,0 +1,120 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package hash + +import ( + "bytes" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/avm/rlp" + "sync" + + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak256 hashers for rlpHash. +var HasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, +} + +// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +var encodeBufferPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +// DerivableList is the input to DeriveSha. +// It is implemented by the 'Transactions' and 'Receipts' types. +// This is internal, do not use these methods. +type DerivableList interface { + Len() int + EncodeIndex(int, *bytes.Buffer) +} + +func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { + buf.Reset() + list.EncodeIndex(i, buf) + // It's really unfortunate that we need to do perform this copy. + // StackTrie holds onto the values until Hash is called, so the values + // written to it must not alias. + return types.CopyBytes(buf.Bytes()) +} + +// DeriveSha creates the tree hashes of transactions and receipts in a block header. +func DeriveSha(list DerivableList) (h types.Hash) { + + sha := HasherPool.Get().(crypto.KeccakState) + defer HasherPool.Put(sha) + sha.Reset() + + valueBuf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(valueBuf) + + for i := 0; i < list.Len(); i++ { + value := encodeForDerive(list, i, valueBuf) + sha.Write(value) + } + sha.Read(h[:]) + return h +} + +var ( + // NilHash sum(nil) + NilHash = types.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + // EmptyUncleHash rlpHash([]*Header(nil)) + EmptyUncleHash = types.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") +) + +func RlpHash(x interface{}) (h types.Hash) { + sha := HasherPool.Get().(crypto.KeccakState) + defer HasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// PrefixedRlpHash writes the prefix into the hasher before rlp-encoding x. +// It's used for typed transactions. +func PrefixedRlpHash(prefix byte, x interface{}) (h types.Hash) { + sha := HasherPool.Get().(crypto.KeccakState) + defer HasherPool.Put(sha) + sha.Reset() + sha.Write([]byte{prefix}) + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// Hash defines a function that returns the sha256 checksum of the data passed in. +// https://github.com/ethereum/consensus-specs/blob/v0.9.3/specs/core/0_beacon-chain.md#hash +func Hash(data []byte) [32]byte { + h, _ := HasherPool.Get().(crypto.KeccakState) + defer HasherPool.Put(h) + h.Reset() + + var b [32]byte + + // The hash interface never returns an error, for that reason + // we are not handling the error below. For reference, it is + // stated here https://golang.org/pkg/hash/#Hash + + // #nosec G104 + h.Write(data) + h.Read(b[:]) + + return b +} diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go new file mode 100644 index 0000000..9080f03 --- /dev/null +++ b/common/hexutil/hexutil.go @@ -0,0 +1,265 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +/* +Package hexutil implements hex encoding with 0x prefix. +This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads. + +# Encoding Rules + +All hex data must have prefix "0x". + +For byte slices, the hex data must be of even length. An empty byte slice +encodes as "0x". + +Integers are encoded using the least amount of digits (no leading zero digits). Their +encoding may be of uneven length. The number zero encodes as "0x0". +*/ +package hexutil + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" +) + +const uintBits = 32 << (uint64(^uint(0)) >> 63) + +// Errors +var ( + ErrEmptyString = &decError{"empty hex string"} + ErrSyntax = &decError{"invalid hex string"} + ErrMissingPrefix = &decError{"hex string without 0x prefix"} + ErrOddLength = &decError{"hex string of odd length"} + ErrEmptyNumber = &decError{"hex string \"0x\""} + ErrLeadingZero = &decError{"hex number with leading zero digits"} + ErrUint64Range = &decError{"hex number > 64 bits"} + ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} + ErrBig256Range = &decError{"hex number > 256 bits"} +) + +type decError struct{ msg string } + +func (err decError) Error() string { return err.msg } + +// Decode decodes a hex string with 0x prefix. +func Decode(input string) ([]byte, error) { + if len(input) == 0 { + return nil, ErrEmptyString + } + if !has0xPrefix(input) { + return nil, ErrMissingPrefix + } + b, err := hex.DecodeString(input[2:]) + if err != nil { + err = mapError(err) + } + return b, err +} + +// MustDecode decodes a hex string with 0x prefix. It panics for invalid input. +func MustDecode(input string) []byte { + dec, err := Decode(input) + if err != nil { + panic(err) + } + return dec +} + +// Encode encodes b as a hex string with 0x prefix. +func Encode(b []byte) string { + enc := make([]byte, len(b)*2+2) + copy(enc, "0x") + hex.Encode(enc[2:], b) + return string(enc) +} + +// DecodeUint64 decodes a hex string with 0x prefix as a quantity. +func DecodeUint64(input string) (uint64, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 64) + if err != nil { + err = mapError(err) + } + return dec, err +} + +// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeUint64(input string) uint64 { + dec, err := DecodeUint64(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeUint64 encodes i as a hex string with 0x prefix. +func EncodeUint64(i uint64) string { + enc := make([]byte, 2, 10) + copy(enc, "0x") + return string(strconv.AppendUint(enc, i, 16)) +} + +var bigWordNibbles int + +func init() { + // This is a weird way to compute the number of nibbles required for big.Word. + // The usual way would be to use constant arithmetic but go vet can't handle that. + b, _ := new(big.Int).SetString("FFFFFFFFFF", 16) + switch len(b.Bits()) { + case 1: + bigWordNibbles = 16 + case 2: + bigWordNibbles = 8 + default: + panic("weird big.Word size") + } +} + +// DecodeBig decodes a hex string with 0x prefix as a quantity. +// Numbers larger than 256 bits are not accepted. +func DecodeBig(input string) (*big.Int, error) { + raw, err := checkNumber(input) + if err != nil { + return nil, err + } + if len(raw) > 64 { + return nil, ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return nil, ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + dec := new(big.Int).SetBits(words) + return dec, nil +} + +// MustDecodeBig decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeBig(input string) *big.Int { + dec, err := DecodeBig(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeBig encodes bigint as a hex string with 0x prefix. +// The sign of the integer is ignored. +func EncodeBig(bigint *big.Int) string { + nbits := bigint.BitLen() + if nbits == 0 { + return "0x0" + } + return fmt.Sprintf("%#x", bigint) +} + +func has0xPrefix(input string) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkNumber(input string) (raw string, err error) { + if len(input) == 0 { + return "", ErrEmptyString + } + if !has0xPrefix(input) { + return "", ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return "", ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return "", ErrLeadingZero + } + return input, nil +} + +const badNibble = ^uint64(0) + +func decodeNibble(in byte) uint64 { + switch { + case in >= '0' && in <= '9': + return uint64(in - '0') + case in >= 'A' && in <= 'F': + return uint64(in - 'A' + 10) + case in >= 'a' && in <= 'f': + return uint64(in - 'a' + 10) + default: + return badNibble + } +} + +func mapError(err error) error { + if err, ok := err.(*strconv.NumError); ok { + switch err.Err { + case strconv.ErrRange: + return ErrUint64Range + case strconv.ErrSyntax: + return ErrSyntax + } + } + if _, ok := err.(hex.InvalidByteError); ok { + return ErrSyntax + } + if err == hex.ErrLength { + return ErrOddLength + } + return err +} + +// CompressNibbles - supports only even number of nibbles +// This method supports only arrays of even nibbles +// +// HI_NIBBLE(b) = (b >> 4) & 0x0F +// LO_NIBBLE(b) = b & 0x0F +func CompressNibbles(nibbles []byte, out *[]byte) { + tmp := (*out)[:0] + for i := 0; i < len(nibbles); i += 2 { + tmp = append(tmp, nibbles[i]<<4|nibbles[i+1]) + } + *out = tmp +} + +// DecompressNibbles - supports only even number of nibbles +// +// HI_NIBBLE(b) = (b >> 4) & 0x0F +// LO_NIBBLE(b) = b & 0x0F +func DecompressNibbles(in []byte, out *[]byte) { + tmp := (*out)[:0] + for i := 0; i < len(in); i++ { + tmp = append(tmp, (in[i]>>4)&0x0F, in[i]&0x0F) + } + *out = tmp +} diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go new file mode 100644 index 0000000..3ce535e --- /dev/null +++ b/common/hexutil/hexutil_test.go @@ -0,0 +1,203 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package hexutil + +import ( + "bytes" + "math/big" + "testing" +) + +type marshalTest struct { + input interface{} + want string +} + +type unmarshalTest struct { + input string + want interface{} + wantErr error // if set, decoding must fail on any platform + wantErr32bit error // if set, decoding must fail on 32bit platforms (used for Uint tests) +} + +var ( + encodeBytesTests = []marshalTest{ + {[]byte{}, "0x"}, + {[]byte{0}, "0x00"}, + {[]byte{0, 0, 1, 2}, "0x00000102"}, + } + + encodeBigTests = []marshalTest{ + {referenceBig("0"), "0x0"}, + {referenceBig("1"), "0x1"}, + {referenceBig("ff"), "0xff"}, + {referenceBig("112233445566778899aabbccddeeff"), "0x112233445566778899aabbccddeeff"}, + {referenceBig("80a7f2c1bcc396c00"), "0x80a7f2c1bcc396c00"}, + {referenceBig("-80a7f2c1bcc396c00"), "-0x80a7f2c1bcc396c00"}, + } + + encodeUint64Tests = []marshalTest{ + {uint64(0), "0x0"}, + {uint64(1), "0x1"}, + {uint64(0xff), "0xff"}, + {uint64(0x1122334455667788), "0x1122334455667788"}, + } + + encodeUintTests = []marshalTest{ + {uint(0), "0x0"}, + {uint(1), "0x1"}, + {uint(0xff), "0xff"}, + {uint(0x11223344), "0x11223344"}, + } + + decodeBytesTests = []unmarshalTest{ + // invalid + {input: ``, wantErr: ErrEmptyString}, + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x0`, wantErr: ErrOddLength}, + {input: `0x023`, wantErr: ErrOddLength}, + {input: `0xxx`, wantErr: ErrSyntax}, + {input: `0x01zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x`, want: []byte{}}, + {input: `0X`, want: []byte{}}, + {input: `0x02`, want: []byte{0x02}}, + {input: `0X02`, want: []byte{0x02}}, + {input: `0xffffffffff`, want: []byte{0xff, 0xff, 0xff, 0xff, 0xff}}, + { + input: `0xffffffffffffffffffffffffffffffffffff`, + want: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + } + + decodeBigTests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + { + input: `0x10000000000000000000000000000000000000000000000000000000000000000`, + wantErr: ErrBig256Range, + }, + // valid + {input: `0x0`, want: big.NewInt(0)}, + {input: `0x2`, want: big.NewInt(0x2)}, + {input: `0x2F2`, want: big.NewInt(0x2f2)}, + {input: `0X2F2`, want: big.NewInt(0x2f2)}, + {input: `0x1122aaff`, want: big.NewInt(0x1122aaff)}, + {input: `0xbBb`, want: big.NewInt(0xbbb)}, + {input: `0xfffffffff`, want: big.NewInt(0xfffffffff)}, + { + input: `0x112233445566778899aabbccddeeff`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `0xffffffffffffffffffffffffffffffffffff`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + { + input: `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`, + want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + } + + decodeUint64Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xfffffffffffffffff`, wantErr: ErrUint64Range}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: uint64(0)}, + {input: `0x2`, want: uint64(0x2)}, + {input: `0x2F2`, want: uint64(0x2f2)}, + {input: `0X2F2`, want: uint64(0x2f2)}, + {input: `0x1122aaff`, want: uint64(0x1122aaff)}, + {input: `0xbbb`, want: uint64(0xbbb)}, + {input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)}, + } +) + +func TestEncode(t *testing.T) { + for _, test := range encodeBytesTests { + enc := Encode(test.input.([]byte)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecode(t *testing.T) { + for _, test := range decodeBytesTests { + dec, err := Decode(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), dec) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeBig(t *testing.T) { + for _, test := range encodeBigTests { + enc := EncodeBig(test.input.(*big.Int)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeBig(t *testing.T) { + for _, test := range decodeBigTests { + dec, err := DecodeBig(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec.Cmp(test.want.(*big.Int)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + enc := EncodeUint64(test.input.(uint64)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeUint64(t *testing.T) { + for _, test := range decodeUint64Tests { + dec, err := DecodeUint64(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} diff --git a/common/hexutil/json.go b/common/hexutil/json.go new file mode 100644 index 0000000..ccf9cb5 --- /dev/null +++ b/common/hexutil/json.go @@ -0,0 +1,323 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package hexutil + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "reflect" + "strconv" +) + +var ( + bytesT = reflect.TypeOf(Bytes(nil)) + bigT = reflect.TypeOf((*Big)(nil)) + uintT = reflect.TypeOf(Uint(0)) + uint64T = reflect.TypeOf(Uint64(0)) +) + +// Bytes marshals/unmarshals as a JSON string with 0x prefix. +// The empty slice marshals as "0x". +type Bytes []byte + +const hexPrefix = `0x` + +// MarshalText implements encoding.TextMarshaler +func (b Bytes) MarshalText() ([]byte, error) { + result := make([]byte, len(b)*2+2) + copy(result, hexPrefix) + hex.Encode(result[2:], b) + return result, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Bytes) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bytesT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Bytes) UnmarshalText(input []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + dec := make([]byte, len(raw)/2) + if _, err = hex.Decode(dec, raw); err != nil { + err = mapError(err) + } else { + *b = dec + } + return err +} + +// String returns the hex encoding of b. +func (b Bytes) String() string { + return Encode(b) +} + +// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalJSON method for fixed-size types. +func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error { + if !isString(input) { + return errNonString(typ) + } + return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ) +} + +// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalText method for fixed-size types. +func UnmarshalFixedText(typname string, input, out []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The +// length of out determines the required input length. This function is commonly used to +// implement the UnmarshalText method for fixed-size types. +func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error { + raw, err := checkText(input, false) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// Big marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +// +// Negative integers are not supported at this time. Attempting to marshal them will +// return an error. Values larger than 256bits are rejected by Unmarshal but will be +// marshaled without error. +type Big big.Int + +// MarshalText implements encoding.TextMarshaler +func (b Big) MarshalText() ([]byte, error) { + return []byte(EncodeBig((*big.Int)(&b))), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Big) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bigT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Big) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 64 { + return ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + var dec big.Int + dec.SetBits(words) + *b = (Big)(dec) + return nil +} + +// ToInt converts b to a big.Int. +func (b *Big) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns the hex encoding of b. +func (b *Big) String() string { + return EncodeBig(b.ToInt()) +} + +// Uint64 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint64 uint64 + +// MarshalText implements encoding.TextMarshaler. +func (b Uint64) MarshalText() ([]byte, error) { + buf := make([]byte, 2, 10) + copy(buf, `0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint64) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uint64T) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Uint64) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 16 { + return ErrUint64Range + } + var dec uint64 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += nib + } + *b = Uint64(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint64) String() string { + return EncodeUint64(uint64(b)) +} + +// Uint marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint uint + +// MarshalText implements encoding.TextMarshaler. +func (b Uint) MarshalText() ([]byte, error) { + return Uint64(b).MarshalText() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uintT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Uint) UnmarshalText(input []byte) error { + var u64 Uint64 + err := u64.UnmarshalText(input) + if u64 > Uint64(^uint(0)) || err == ErrUint64Range { + return ErrUintRange + } else if err != nil { + return err + } + *b = Uint(u64) + return nil +} + +// String returns the hex encoding of b. +func (b Uint) String() string { + return EncodeUint64(uint64(b)) +} + +func isString(input []byte) bool { + return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' +} + +func bytesHave0xPrefix(input []byte) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkText(input []byte, wantPrefix bool) ([]byte, error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if bytesHave0xPrefix(input) { + input = input[2:] + } else if wantPrefix { + return nil, ErrMissingPrefix + } + if len(input)%2 != 0 { + return nil, ErrOddLength + } + return input, nil +} + +func checkNumberText(input []byte) (raw []byte, err error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if !bytesHave0xPrefix(input) { + return nil, ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return nil, ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return nil, ErrLeadingZero + } + return input, nil +} + +func wrapTypeError(err error, typ reflect.Type) error { + if _, ok := err.(*decError); ok { + return &json.UnmarshalTypeError{Value: err.Error(), Type: typ} + } + return err +} + +func errNonString(typ reflect.Type) error { + return &json.UnmarshalTypeError{Value: "non-string", Type: typ} +} diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go new file mode 100644 index 0000000..accc7a4 --- /dev/null +++ b/common/hexutil/json_test.go @@ -0,0 +1,374 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package hexutil + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "math/big" + "testing" +) + +func checkError(t *testing.T, input string, got, want error) bool { + if got == nil { + if want != nil { + t.Errorf("input %s: got no error, want %q", input, want) + return false + } + return true + } + if want == nil { + t.Errorf("input %s: unexpected error %q", input, got) + } else if got.Error() != want.Error() { + t.Errorf("input %s: got error %q, want %q", input, got, want) + } + return false +} + +func referenceBig(s string) *big.Int { + b, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid") + } + return b +} + +func referenceBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var errJSONEOF = errors.New("unexpected end of JSON input") + +var unmarshalBytesTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bytesT)}, + {input: "10", wantErr: errNonString(bytesT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bytesT)}, + {input: `"0x0"`, wantErr: wrapTypeError(ErrOddLength, bytesT)}, + {input: `"0xxx"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + {input: `"0x01zz01"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + + // valid encoding + {input: `""`, want: referenceBytes("")}, + {input: `"0x"`, want: referenceBytes("")}, + {input: `"0x02"`, want: referenceBytes("02")}, + {input: `"0X02"`, want: referenceBytes("02")}, + {input: `"0xffffffffff"`, want: referenceBytes("ffffffffff")}, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBytes("ffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBytes(t *testing.T) { + for _, test := range unmarshalBytesTests { + var v Bytes + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), v) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Bytes + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBytes(t *testing.T) { + for _, test := range encodeBytesTests { + in := test.input.([]byte) + out, err := json.Marshal(Bytes(in)) + if err != nil { + t.Errorf("%x: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := Bytes(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalBigTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bigT)}, + {input: "10", wantErr: errNonString(bigT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bigT)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, bigT)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, bigT)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + { + input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`, + wantErr: wrapTypeError(ErrBig256Range, bigT), + }, + + // valid encoding + {input: `""`, want: big.NewInt(0)}, + {input: `"0x0"`, want: big.NewInt(0)}, + {input: `"0x2"`, want: big.NewInt(0x2)}, + {input: `"0x2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0X2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)}, + {input: `"0xbBb"`, want: big.NewInt(0xbbb)}, + {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)}, + { + input: `"0x112233445566778899aabbccddeeff"`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + { + input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBig(t *testing.T) { + for _, test := range unmarshalBigTests { + var v Big + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want) + continue + } + } +} + +func BenchmarkUnmarshalBig(b *testing.B) { + input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Big + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBig(t *testing.T) { + for _, test := range encodeBigTests { + in := test.input.(*big.Int) + out, err := json.Marshal((*Big)(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (*Big)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalUint64Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uint64T)}, + {input: "10", wantErr: errNonString(uint64T)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint64T)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint64T)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint64T)}, + {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUint64Range, uint64T)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + + // valid encoding + {input: `""`, want: uint64(0)}, + {input: `"0x0"`, want: uint64(0)}, + {input: `"0x2"`, want: uint64(0x2)}, + {input: `"0x2F2"`, want: uint64(0x2f2)}, + {input: `"0X2F2"`, want: uint64(0x2f2)}, + {input: `"0x1122aaff"`, want: uint64(0x1122aaff)}, + {input: `"0xbbb"`, want: uint64(0xbbb)}, + {input: `"0xffffffffffffffff"`, want: uint64(0xffffffffffffffff)}, +} + +func TestUnmarshalUint64(t *testing.T) { + for _, test := range unmarshalUint64Tests { + var v Uint64 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint64(v) != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint64(b *testing.B) { + input := []byte(`"0x123456789abcdf"`) + for i := 0; i < b.N; i++ { + var v Uint64 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + in := test.input.(uint64) + out, err := json.Marshal(Uint64(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint64)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +func TestMarshalUint(t *testing.T) { + for _, test := range encodeUintTests { + in := test.input.(uint) + out, err := json.Marshal(Uint(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var ( + // These are variables (not constants) to avoid constant overflow + // checks in the compiler on 32bit platforms. + maxUint33bits = uint64(^uint32(0)) + 1 + maxUint64bits = ^uint64(0) +) + +var unmarshalUintTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uintT)}, + {input: "10", wantErr: errNonString(uintT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uintT)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uintT)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uintT)}, + {input: `"0x100000000"`, want: uint(maxUint33bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, + {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUintRange, uintT)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + + // valid encoding + {input: `""`, want: uint(0)}, + {input: `"0x0"`, want: uint(0)}, + {input: `"0x2"`, want: uint(0x2)}, + {input: `"0x2F2"`, want: uint(0x2f2)}, + {input: `"0X2F2"`, want: uint(0x2f2)}, + {input: `"0x1122aaff"`, want: uint(0x1122aaff)}, + {input: `"0xbbb"`, want: uint(0xbbb)}, + {input: `"0xffffffff"`, want: uint(0xffffffff)}, + {input: `"0xffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, +} + +func TestUnmarshalUint(t *testing.T) { + for _, test := range unmarshalUintTests { + var v Uint + err := json.Unmarshal([]byte(test.input), &v) + if uintBits == 32 && test.wantErr32bit != nil { + checkError(t, test.input, err, test.wantErr32bit) + continue + } + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint(v) != test.want.(uint) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func TestUnmarshalFixedUnprefixedText(t *testing.T) { + tests := []struct { + input string + want []byte + wantErr error + }{ + {input: "0x2", wantErr: ErrOddLength}, + {input: "2", wantErr: ErrOddLength}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + // check that output is not modified for partially correct input + {input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + {input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + // valid inputs + {input: "44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + {input: "0x44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + } + + for _, test := range tests { + out := make([]byte, 4) + err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out) + switch { + case err == nil && test.wantErr != nil: + t.Errorf("%q: got no error, expected %q", test.input, test.wantErr) + case err != nil && test.wantErr == nil: + t.Errorf("%q: unexpected error %q", test.input, err) + case err != nil && err.Error() != test.wantErr.Error(): + t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr) + } + if test.want != nil && !bytes.Equal(out, test.want) { + t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want) + } + } +} diff --git a/common/interface/Transaction.go b/common/interface/Transaction.go new file mode 100644 index 0000000..3c27442 --- /dev/null +++ b/common/interface/Transaction.go @@ -0,0 +1,34 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package _interface + +//import ( +// "github.com/astranetworld/ast/common/types" +// "github.com/gogo/protobuf/proto" +//) +// +//type ITransaction interface { +// MarshalTo(data []byte) (n int, err error) +// ToProtoMessage() proto.Message +// Unmarshal(data []byte) error +// Marshal() ([]byte, error) +// Type() uint8 +// ChainId() uint256.Int +// Data() []byte +//} +// +//type Transactions []ITransaction diff --git a/common/interfaces.go b/common/interfaces.go new file mode 100644 index 0000000..ab95f4b --- /dev/null +++ b/common/interfaces.go @@ -0,0 +1,150 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "context" + "github.com/astranetworld/ast/common/block" + "github.com/astranetworld/ast/common/message" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "google.golang.org/protobuf/proto" +) + +// Service is a struct that can be registered into a ServiceRegistry for +// easy dependency management. +type Service interface { + // Start spawns any goroutines required by the service. + //Start() + // Stop terminates all goroutines belonging to the service, + // blocking until they are all terminated. + Stop() error + // Status returns error if the service is not considered healthy. + //Status() error +} + +type IDownloader interface { + SyncHeader() error + SyncBody() error + SyncTx() error + Start() error + Close() error + IsDownloading() bool + ConnHandler([]byte, peer.ID) error +} + +type ConnHandler func([]byte, peer.ID) error + +type ProtocolHandshakeFn func(peer IPeer, genesisHash types.Hash, currentHeight *uint256.Int) (Peer, bool) +type ProtocolHandshakeInfo func() (types.Hash, *uint256.Int, error) + +type INetwork interface { + WriterMessage(messageType message.MessageType, payload []byte, peer peer.ID) error + //BroadcastMessage(messageType message.MessageType, payload []byte) (int, error) + SetHandler(message.MessageType, ConnHandler) error + ClosePeer(id peer.ID) error + Start() error + Host() host.Host + PeerCount() int + Bootstrapped() bool +} + +type IPeer interface { + ID() peer.ID + Write(msg message.IMessage) error + WriteMsg(messageType message.MessageType, payload []byte) error + SetHandler(message.MessageType, ConnHandler) error + ClearHandler(message.MessageType) error + Close() error +} + +type IPubSub interface { + JoinTopic(topic string) (*pubsub.Topic, error) + Publish(topic string, msg proto.Message) error + GetTopics() []string + Start() error +} + +type IStateDB interface { + CreateAccount(types.Address) + + SubBalance(addr types.Address, amount uint256.Int) + AddBalance(addr types.Address, amount uint256.Int) + GetBalance(addr types.Address) uint256.Int + + GetNonce(addr types.Address) uint64 + SetNonce(addr types.Address, nonce uint64) + + GetCodeHash(addr types.Address) types.Hash + GetCode(addr types.Address) []byte + SetCode(addr types.Address, code []byte) + GetCodeSize(addr types.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(types.Address, types.Hash) types.Hash + GetState(types.Address, types.Hash) types.Hash + SetState(types.Address, types.Hash, types.Hash) + + Suicide(types.Address) bool + HasSuicided(types.Address) bool + + Exist(types.Address) bool + Empty(types.Address) bool + + PrepareAccessList(sender types.Address, dest *types.Address, precompiles []types.Address, list transaction.AccessList) + AddressInAccessList(addr types.Address) bool + SlotInAccessList(addr types.Address, slot types.Hash) (addressOk bool, slotOk bool) + AddAddressToAccessList(addr types.Address) + AddSlotToAccessList(addr types.Address, slot types.Hash) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*block.Log) + GetLogs(hash types.Hash, blockHash types.Hash) []*block.Log + + TxIndex() int + Prepare(thash types.Hash, ti int) + + Error() error +} +type ChainStateReader interface { + BalanceAt(ctx context.Context, account types.Address, blockNumber uint256.Int) (uint256.Int, error) + StorageAt(ctx context.Context, account types.Address, key types.Hash, blockNumber uint256.Int) ([]byte, error) + CodeAt(ctx context.Context, account types.Address, blockNumber uint256.Int) ([]byte, error) + NonceAt(ctx context.Context, account types.Address, blockNumber uint256.Int) (uint64, error) +} + +type ITxsPool interface { + Service + Has(hash types.Hash) bool + Pending(enforceTips bool) map[types.Address][]*transaction.Transaction + GetTransaction() ([]*transaction.Transaction, error) + GetTx(hash types.Hash) *transaction.Transaction + AddRemotes(txs []*transaction.Transaction) []error + AddLocal(tx *transaction.Transaction) error + Stats() (int, int, int, int) + Nonce(addr types.Address) uint64 + Content() (map[types.Address][]*transaction.Transaction, map[types.Address][]*transaction.Transaction) +} diff --git a/common/math/big.go b/common/math/big.go new file mode 100644 index 0000000..e88ce40 --- /dev/null +++ b/common/math/big.go @@ -0,0 +1,277 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package math provides integer math utilities. +package math + +import ( + "fmt" + "math/big" + + "github.com/holiman/uint256" +) + +// Various big integer limit values. +var ( + tt255 = BigPow(2, 255) + tt256 = BigPow(2, 256) + tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) + tt63 = BigPow(2, 63) + MaxBig256 = new(big.Int).Set(tt256m1) + MaxBig63 = new(big.Int).Sub(tt63, big.NewInt(1)) +) + +const ( + // number of bits in a big.Word + wordBits = 32 << (uint64(^big.Word(0)) >> 63) + // number of bytes in a big.Word + wordBytes = wordBits / 8 +) + +// HexOrDecimal256 marshals big.Int as hex or decimal. +type HexOrDecimal256 big.Int + +// NewHexOrDecimal256 creates a new HexOrDecimal256 +func NewHexOrDecimal256(x int64) *HexOrDecimal256 { + b := big.NewInt(x) + h := HexOrDecimal256(*b) + return &h +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *HexOrDecimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = HexOrDecimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *HexOrDecimal256) MarshalText() ([]byte, error) { + if i == nil { + return []byte("0x0"), nil + } + return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil +} + +// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, +// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) +type Decimal256 big.Int + +// NewHexOrDecimal256 creates a new Decimal256 +func NewDecimal256(x int64) *Decimal256 { + b := big.NewInt(x) + d := Decimal256(*b) + return &d +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *Decimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = Decimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *Decimal256) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// String implements Stringer. +func (i *Decimal256) String() string { + if i == nil { + return "0" + } + return fmt.Sprintf("%#d", (*big.Int)(i)) +} + +// ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. +// Leading zeros are accepted. The empty string parses as zero. +func ParseBig256(s string) (*big.Int, bool) { + if s == "" { + return new(big.Int), true + } + var bigint *big.Int + var ok bool + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + bigint, ok = new(big.Int).SetString(s[2:], 16) + } else { + bigint, ok = new(big.Int).SetString(s, 10) + } + if ok && bigint.BitLen() > 256 { + bigint, ok = nil, false + } + return bigint, ok +} + +// MustParseBig256 parses s as a 256 bit big integer and panics if the string is invalid. +func MustParseBig256(s string) *big.Int { + v, ok := ParseBig256(s) + if !ok { + panic("invalid 256 bit integer: " + s) + } + return v +} + +// BigPow returns a ** b as a big integer. +func BigPow(a, b int64) *big.Int { + r := big.NewInt(a) + return r.Exp(r, big.NewInt(b), nil) +} + +// BigMax returns the larger of x or y. +func BigMax(x, y *big.Int) *big.Int { + if x.Cmp(y) < 0 { + return y + } + return x +} + +// BigMin returns the smaller of x or y. +func BigMin(x, y *big.Int) *big.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + +// U256Min returns the smaller of x or y. +func U256Min(x, y *uint256.Int) *uint256.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + +// Min256 returns the smaller of x or y. +func Min256(x, y *uint256.Int) *uint256.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + +// FirstBitSet returns the index of the first 1 bit in v, counting from LSB. +func FirstBitSet(v *big.Int) int { + for i := 0; i < v.BitLen(); i++ { + if v.Bit(i) > 0 { + return i + } + } + return v.BitLen() +} + +// PaddedBigBytes encodes a big integer as a big-endian byte slice. The length +// of the slice is at least n bytes. +func PaddedBigBytes(bigint *big.Int, n int) []byte { + if bigint.BitLen()/8 >= n { + return bigint.Bytes() + } + ret := make([]byte, n) + ReadBits(bigint, ret) + return ret +} + +// bigEndianByteAt returns the byte at position n, +// in Big-Endian encoding +// So n==0 returns the least significant byte +func bigEndianByteAt(bigint *big.Int, n int) byte { + words := bigint.Bits() + // Check word-bucket the byte will reside in + i := n / wordBytes + if i >= len(words) { + return byte(0) + } + word := words[i] + // Offset of the byte + shift := 8 * uint(n%wordBytes) + + return byte(word >> shift) +} + +// Byte returns the byte at position n, +// with the supplied padlength in Little-Endian encoding. +// n==0 returns the MSB +// Example: bigint '5', padlength 32, n=31 => 5 +func Byte(bigint *big.Int, padlength, n int) byte { + if n >= padlength { + return byte(0) + } + return bigEndianByteAt(bigint, padlength-1-n) +} + +// ReadBits encodes the absolute value of bigint as big-endian bytes. Callers must ensure +// that buf has enough space. If buf is too short the result will be incomplete. +func ReadBits(bigint *big.Int, buf []byte) { + i := len(buf) + for _, d := range bigint.Bits() { + for j := 0; j < wordBytes && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } +} + +// U256 encodes as a 256 bit two's complement number. This operation is destructive. +func U256(x *big.Int) *big.Int { + return x.And(x, tt256m1) +} + +// U256Bytes converts a big Int into a 256bit EVM number. +// This operation is destructive. +func U256Bytes(n *big.Int) []byte { + return PaddedBigBytes(U256(n), 32) +} + +// S256 interprets x as a two's complement number. +// x must not exceed 256 bits (the result is undefined if it does) and is not modified. +// +// S256(0) = 0 +// S256(1) = 1 +// S256(2**255) = -2**255 +// S256(2**256-1) = -1 +func S256(x *big.Int) *big.Int { + if x.Cmp(tt255) < 0 { + return x + } + return new(big.Int).Sub(x, tt256) +} + +// Exp implements exponentiation by squaring. +// Exp returns a newly-allocated big integer and does not change +// base or exponent. The result is truncated to 256 bits. +// +// Courtesy @karalabe and @chfast +func Exp(base, exponent *big.Int) *big.Int { + result := big.NewInt(1) + + for _, word := range exponent.Bits() { + for i := 0; i < wordBits; i++ { + if word&1 == 1 { + U256(result.Mul(result, base)) + } + U256(base.Mul(base, base)) + word >>= 1 + } + } + return result +} diff --git a/common/math/integer.go b/common/math/integer.go new file mode 100644 index 0000000..a6a2695 --- /dev/null +++ b/common/math/integer.go @@ -0,0 +1,117 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package math + +import ( + "crypto/rand" + "fmt" + "math" + "math/big" + "math/bits" + "strconv" +) + +// Integer limit values. +const ( + MaxInt8 = 1<<7 - 1 + MinInt8 = -1 << 7 + MaxInt16 = 1<<15 - 1 + MinInt16 = -1 << 15 + MaxInt32 = 1<<31 - 1 + MinInt32 = -1 << 31 + MaxInt64 = 1<<63 - 1 + MinInt64 = -1 << 63 + MaxUint8 = 1<<8 - 1 + MaxUint16 = 1<<16 - 1 + MaxUint32 = 1<<32 - 1 + MaxUint64 = 1<<64 - 1 +) + +// HexOrDecimal64 marshals uint64 as hex or decimal. +type HexOrDecimal64 uint64 + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *HexOrDecimal64) UnmarshalText(input []byte) error { + int, ok := ParseUint64(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = HexOrDecimal64(int) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i HexOrDecimal64) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%#x", uint64(i))), nil +} + +// ParseUint64 parses s as an integer in decimal or hexadecimal syntax. +// Leading zeros are accepted. The empty string parses as zero. +func ParseUint64(s string) (uint64, bool) { + if s == "" { + return 0, true + } + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + v, err := strconv.ParseUint(s[2:], 16, 64) + return v, err == nil + } + v, err := strconv.ParseUint(s, 10, 64) + return v, err == nil +} + +// MustParseUint64 parses s as an integer and panics if the string is invalid. +func MustParseUint64(s string) uint64 { + v, ok := ParseUint64(s) + if !ok { + panic("invalid unsigned 64 bit integer: " + s) + } + return v +} + +// SafeSub returns x-y and checks for overflow. +func SafeSub(x, y uint64) (uint64, bool) { + diff, borrowOut := bits.Sub64(x, y, 0) + return diff, borrowOut != 0 +} + +// SafeAdd returns x+y and checks for overflow. +func SafeAdd(x, y uint64) (uint64, bool) { + sum, carryOut := bits.Add64(x, y, 0) + return sum, carryOut != 0 +} + +// SafeMul returns x*y and checks for overflow. +func SafeMul(x, y uint64) (uint64, bool) { + hi, lo := bits.Mul64(x, y) + return lo, hi != 0 +} + +// AbsoluteDifference is a utility method that given 2 int64, it returns the absolute value of their difference in uint64 format. +func AbsoluteDifference(x, y uint64) uint64 { + if x > y { + return x - y + } + return y - x +} + +func RandInt64() (int64, error) { + n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + return 0, err + } + return n.Int64(), nil +} diff --git a/common/math/integer_test.go b/common/math/integer_test.go new file mode 100644 index 0000000..a9e3fae --- /dev/null +++ b/common/math/integer_test.go @@ -0,0 +1,125 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package math + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type operation byte + +const ( + sub operation = iota + add + mul +) + +func TestOverflow(t *testing.T) { + for i, test := range []struct { + x uint64 + y uint64 + overflow bool + op operation + }{ + // add operations + {MaxUint64, 1, true, add}, + {MaxUint64 - 1, 1, false, add}, + + // sub operations + {0, 1, true, sub}, + {0, 0, false, sub}, + + // mul operations + {0, 0, false, mul}, + {10, 10, false, mul}, + {MaxUint64, 2, true, mul}, + {MaxUint64, 1, false, mul}, + } { + var overflows bool + switch test.op { + case sub: + _, overflows = SafeSub(test.x, test.y) + case add: + _, overflows = SafeAdd(test.x, test.y) + case mul: + _, overflows = SafeMul(test.x, test.y) + } + + if test.overflow != overflows { + t.Errorf("%d failed. Expected test to be %v, got %v", i, test.overflow, overflows) + } + } +} + +func TestHexOrDecimal64(t *testing.T) { + tests := []struct { + input string + num uint64 + ok bool + }{ + {"", 0, true}, + {"0", 0, true}, + {"0x0", 0, true}, + {"12345678", 12345678, true}, + {"0x12345678", 0x12345678, true}, + {"0X12345678", 0x12345678, true}, + // Tests for leading zero behaviour: + {"0123456789", 123456789, true}, // note: not octal + {"0x00", 0, true}, + {"0x012345678abc", 0x12345678abc, true}, + // Invalid syntax: + {"abcdef", 0, false}, + {"0xgg", 0, false}, + // Doesn't fit into 64 bits: + {"18446744073709551617", 0, false}, + } + for _, test := range tests { + var num HexOrDecimal64 + err := num.UnmarshalText([]byte(test.input)) + if (err == nil) != test.ok { + t.Errorf("ParseUint64(%q) -> (err == nil) = %t, want %t", test.input, err == nil, test.ok) + continue + } + if err == nil && uint64(num) != test.num { + t.Errorf("ParseUint64(%q) -> %d, want %d", test.input, num, test.num) + } + } +} + +func TestMustParseUint64(t *testing.T) { + if v := MustParseUint64("12345"); v != 12345 { + t.Errorf(`MustParseUint64("12345") = %d, want 12345`, v) + } +} + +func TestMustParseUint64Panic(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("MustParseBig should've panicked") + } + }() + MustParseUint64("ggg") +} + +func TestAbsoluteDifference(t *testing.T) { + x1 := uint64(99) + x2 := uint64(45) + assert.Equal(t, AbsoluteDifference(x1, x2), x1-x2) + assert.Equal(t, AbsoluteDifference(x2, x1), x1-x2) +} diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go new file mode 100644 index 0000000..e07af28 --- /dev/null +++ b/common/mclock/mclock.go @@ -0,0 +1,127 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +// Package mclock is a wrapper for a monotonic clock source +package mclock + +import ( + "time" + + _ "unsafe" // for go:linkname +) + +//go:noescape +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// AbsTime represents absolute monotonic time. +type AbsTime int64 + +// Now returns the current absolute monotonic time. +func Now() AbsTime { + return AbsTime(nanotime()) +} + +// Add returns t + d as absolute time. +func (t AbsTime) Add(d time.Duration) AbsTime { + return t + AbsTime(d) +} + +// Sub returns t - t2 as a duration. +func (t AbsTime) Sub(t2 AbsTime) time.Duration { + return time.Duration(t - t2) +} + +// The Clock interface makes it possible to replace the monotonic system clock with +// a simulated clock. +type Clock interface { + Now() AbsTime + Sleep(time.Duration) + NewTimer(time.Duration) ChanTimer + After(time.Duration) <-chan AbsTime + AfterFunc(d time.Duration, f func()) Timer +} + +// Timer is a cancellable event created by AfterFunc. +type Timer interface { + // Stop cancels the timer. It returns false if the timer has already + // expired or been stopped. + Stop() bool +} + +// ChanTimer is a cancellable event created by NewTimer. +type ChanTimer interface { + Timer + + // The channel returned by C receives a value when the timer expires. + C() <-chan AbsTime + // Reset reschedules the timer with a new timeout. + // It should be invoked only on stopped or expired timers with drained channels. + Reset(time.Duration) +} + +// System implements Clock using the system clock. +type System struct{} + +// Now returns the current monotonic time. +func (c System) Now() AbsTime { + return Now() +} + +// Sleep blocks for the given duration. +func (c System) Sleep(d time.Duration) { + time.Sleep(d) +} + +// NewTimer creates a timer which can be rescheduled. +func (c System) NewTimer(d time.Duration) ChanTimer { + ch := make(chan AbsTime, 1) + t := time.AfterFunc(d, func() { + // This send is non-blocking because that's how time.Timer + // behaves. It doesn't matter in the happy case, but does + // when Reset is misused. + select { + case ch <- c.Now(): + default: + } + }) + return &systemTimer{t, ch} +} + +// After returns a channel which receives the current time after d has elapsed. +func (c System) After(d time.Duration) <-chan AbsTime { + ch := make(chan AbsTime, 1) + time.AfterFunc(d, func() { ch <- c.Now() }) + return ch +} + +// AfterFunc runs f on a new goroutine after the duration has elapsed. +func (c System) AfterFunc(d time.Duration, f func()) Timer { + return time.AfterFunc(d, f) +} + +type systemTimer struct { + *time.Timer + ch <-chan AbsTime +} + +func (st *systemTimer) Reset(d time.Duration) { + st.Timer.Reset(d) +} + +func (st *systemTimer) C() <-chan AbsTime { + return st.ch +} diff --git a/common/mclock/mclock.s b/common/mclock/mclock.s new file mode 100644 index 0000000..99a7a87 --- /dev/null +++ b/common/mclock/mclock.s @@ -0,0 +1 @@ +// This file exists in order to be able to use go:linkname. diff --git a/common/mclock/simclock.go b/common/mclock/simclock.go new file mode 100644 index 0000000..6dbe7fe --- /dev/null +++ b/common/mclock/simclock.go @@ -0,0 +1,209 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package mclock + +import ( + "container/heap" + "sync" + "time" +) + +// Simulated implements a virtual Clock for reproducible time-sensitive tests. It +// simulates a scheduler on a virtual timescale where actual processing takes zero time. +// +// The virtual clock doesn't advance on its own, call Run to advance it and execute timers. +// Since there is no way to influence the Go scheduler, testing timeout behaviour involving +// goroutines needs special care. A good way to test such timeouts is as follows: First +// perform the action that is supposed to time out. Ensure that the timer you want to test +// is created. Then run the clock until after the timeout. Finally observe the effect of +// the timeout using a channel or semaphore. +type Simulated struct { + now AbsTime + scheduled simTimerHeap + mu sync.RWMutex + cond *sync.Cond +} + +// simTimer implements ChanTimer on the virtual clock. +type simTimer struct { + at AbsTime + index int // position in s.scheduled + s *Simulated + do func() + ch <-chan AbsTime +} + +func (s *Simulated) init() { + if s.cond == nil { + s.cond = sync.NewCond(&s.mu) + } +} + +// Run moves the clock by the given duration, executing all timers before that duration. +func (s *Simulated) Run(d time.Duration) { + s.mu.Lock() + s.init() + + end := s.now + AbsTime(d) + var do []func() + for len(s.scheduled) > 0 && s.scheduled[0].at <= end { + ev := heap.Pop(&s.scheduled).(*simTimer) + do = append(do, ev.do) + } + s.now = end + s.mu.Unlock() + + for _, fn := range do { + fn() + } +} + +// ActiveTimers returns the number of timers that haven't fired. +func (s *Simulated) ActiveTimers() int { + s.mu.RLock() + defer s.mu.RUnlock() + + return len(s.scheduled) +} + +// WaitForTimers waits until the clock has at least n scheduled timers. +func (s *Simulated) WaitForTimers(n int) { + s.mu.Lock() + defer s.mu.Unlock() + s.init() + + for len(s.scheduled) < n { + s.cond.Wait() + } +} + +// Now returns the current virtual time. +func (s *Simulated) Now() AbsTime { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.now +} + +// Sleep blocks until the clock has advanced by d. +func (s *Simulated) Sleep(d time.Duration) { + <-s.After(d) +} + +// NewTimer creates a timer which fires when the clock has advanced by d. +func (s *Simulated) NewTimer(d time.Duration) ChanTimer { + s.mu.Lock() + defer s.mu.Unlock() + + ch := make(chan AbsTime, 1) + var timer *simTimer + timer = s.schedule(d, func() { ch <- timer.at }) + timer.ch = ch + return timer +} + +// After returns a channel which receives the current time after the clock +// has advanced by d. +func (s *Simulated) After(d time.Duration) <-chan AbsTime { + return s.NewTimer(d).C() +} + +// AfterFunc runs fn after the clock has advanced by d. Unlike with the system +// clock, fn runs on the goroutine that calls Run. +func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer { + s.mu.Lock() + defer s.mu.Unlock() + + return s.schedule(d, fn) +} + +func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer { + s.init() + + at := s.now + AbsTime(d) + ev := &simTimer{do: fn, at: at, s: s} + heap.Push(&s.scheduled, ev) + s.cond.Broadcast() + return ev +} + +func (ev *simTimer) Stop() bool { + ev.s.mu.Lock() + defer ev.s.mu.Unlock() + + if ev.index < 0 { + return false + } + heap.Remove(&ev.s.scheduled, ev.index) + ev.s.cond.Broadcast() + ev.index = -1 + return true +} + +func (ev *simTimer) Reset(d time.Duration) { + if ev.ch == nil { + panic("mclock: Reset() on timer created by AfterFunc") + } + + ev.s.mu.Lock() + defer ev.s.mu.Unlock() + ev.at = ev.s.now.Add(d) + if ev.index < 0 { + heap.Push(&ev.s.scheduled, ev) // already expired + } else { + heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule + } + ev.s.cond.Broadcast() +} + +func (ev *simTimer) C() <-chan AbsTime { + if ev.ch == nil { + panic("mclock: C() on timer created by AfterFunc") + } + return ev.ch +} + +type simTimerHeap []*simTimer + +func (h *simTimerHeap) Len() int { + return len(*h) +} + +func (h *simTimerHeap) Less(i, j int) bool { + return (*h)[i].at < (*h)[j].at +} + +func (h *simTimerHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + (*h)[i].index = i + (*h)[j].index = j +} + +func (h *simTimerHeap) Push(x interface{}) { + t := x.(*simTimer) + t.index = len(*h) + *h = append(*h, t) +} + +func (h *simTimerHeap) Pop() interface{} { + end := len(*h) - 1 + t := (*h)[end] + t.index = -1 + (*h)[end] = nil + *h = (*h)[:end] + return t +} diff --git a/common/mclock/simclock_test.go b/common/mclock/simclock_test.go new file mode 100644 index 0000000..ffd02d5 --- /dev/null +++ b/common/mclock/simclock_test.go @@ -0,0 +1,162 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package mclock + +import ( + "testing" + "time" +) + +var _ Clock = System{} +var _ Clock = new(Simulated) + +func TestSimulatedAfter(t *testing.T) { + var ( + timeout = 30 * time.Minute + offset = 99 * time.Hour + adv = 11 * time.Minute + c Simulated + ) + c.Run(offset) + + end := c.Now().Add(timeout) + ch := c.After(timeout) + for c.Now() < end.Add(-adv) { + c.Run(adv) + select { + case <-ch: + t.Fatal("Timer fired early") + default: + } + } + + c.Run(adv) + select { + case stamp := <-ch: + want := AbsTime(0).Add(offset).Add(timeout) + if stamp != want { + t.Errorf("Wrong time sent on timer channel: got %v, want %v", stamp, want) + } + default: + t.Fatal("Timer didn't fire") + } +} + +func TestSimulatedAfterFunc(t *testing.T) { + var c Simulated + + called1 := false + timer1 := c.AfterFunc(100*time.Millisecond, func() { called1 = true }) + if c.ActiveTimers() != 1 { + t.Fatalf("%d active timers, want one", c.ActiveTimers()) + } + if fired := timer1.Stop(); !fired { + t.Fatal("Stop returned false even though timer didn't fire") + } + if c.ActiveTimers() != 0 { + t.Fatalf("%d active timers, want zero", c.ActiveTimers()) + } + if called1 { + t.Fatal("timer 1 called") + } + if fired := timer1.Stop(); fired { + t.Fatal("Stop returned true after timer was already stopped") + } + + called2 := false + timer2 := c.AfterFunc(100*time.Millisecond, func() { called2 = true }) + c.Run(50 * time.Millisecond) + if called2 { + t.Fatal("timer 2 called") + } + c.Run(51 * time.Millisecond) + if !called2 { + t.Fatal("timer 2 not called") + } + if fired := timer2.Stop(); fired { + t.Fatal("Stop returned true after timer has fired") + } +} + +func TestSimulatedSleep(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + done = make(chan AbsTime, 1) + ) + go func() { + c.Sleep(timeout) + done <- c.Now() + }() + + c.WaitForTimers(1) + c.Run(2 * timeout) + select { + case stamp := <-done: + want := AbsTime(2 * timeout) + if stamp != want { + t.Errorf("Wrong time after sleep: got %v, want %v", stamp, want) + } + case <-time.After(5 * time.Second): + t.Fatal("Sleep didn't return in time") + } +} + +func TestSimulatedTimerReset(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + ) + timer := c.NewTimer(timeout) + c.Run(2 * timeout) + select { + case ftime := <-timer.C(): + if ftime != AbsTime(timeout) { + t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(timeout)) + } + default: + t.Fatal("timer didn't fire") + } + + timer.Reset(timeout) + c.Run(2 * timeout) + select { + case ftime := <-timer.C(): + if ftime != AbsTime(3*timeout) { + t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(3*timeout)) + } + default: + t.Fatal("timer didn't fire again") + } +} + +func TestSimulatedTimerStop(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + ) + timer := c.NewTimer(timeout) + c.Run(2 * timeout) + if timer.Stop() { + t.Errorf("Stop returned true for fired timer") + } + select { + case <-timer.C(): + default: + t.Fatal("timer didn't fire") + } +} diff --git a/common/message/message.go b/common/message/message.go new file mode 100644 index 0000000..a14c4c8 --- /dev/null +++ b/common/message/message.go @@ -0,0 +1,71 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package message + +import ( + "github.com/libp2p/go-libp2p/core/peer" +) + +const ( + MsgConnect = MessageType(iota + 1) + MsgPingReq + MsgPingResp + MsgSystem + MsgAppHandshake + MsgApplication + MsgDownloader + MsgNewBlock + MsgDisconnect + MsgTransaction + MsgTypeFirstInvalid +) + +type MessageType uint8 + +func (mt MessageType) IsValid() bool { + return mt >= MsgConnect && mt < MsgTypeFirstInvalid +} + +// String implements the stringer interface. +func (mt MessageType) String() string { + switch mt { + case MsgConnect: + return "Connect" + case MsgPingReq: + return "PingRequest" + case MsgPingResp: + return "PingResponse" + case MsgAppHandshake: + return "AppHandshake" + case MsgApplication: + return "MsgApplication" + case MsgDownloader: + return "MsgDownloader" + case MsgTransaction: + return "MsgTransaction" + default: + return "unknown" + } +} + +type IMessage interface { + Type() MessageType + Peer() peer.ID + Broadcast() bool + Encode() ([]byte, error) + Decode(MessageType, []byte) error +} diff --git a/common/message/topic_default.go b/common/message/topic_default.go new file mode 100644 index 0000000..dbd1156 --- /dev/null +++ b/common/message/topic_default.go @@ -0,0 +1,30 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package message + +var TopicMappings = map[string]interface{}{ + GossipBlockMessage: struct{}{}, + GossipSyncState: struct{}{}, + GossipTransactionMessage: struct{}{}, +} + +const ( + GossipPrefix = "ast/1/" + GossipBlockMessage = GossipPrefix + "new_block" + GossipSyncState = GossipPrefix + "sync_state" + GossipTransactionMessage = GossipPrefix + "new_transaction" +) diff --git a/common/paths/paths.go b/common/paths/paths.go new file mode 100644 index 0000000..9ccc839 --- /dev/null +++ b/common/paths/paths.go @@ -0,0 +1,88 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package paths + +import ( + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +const dirname = "ast" + +// DefaultDataDir is the default data directory to use for the databases and other +// persistence requirements. +func DefaultDataDir() string { + // Try to place the data folder in the user's home dir + home := homeDir() + if home != "" { + switch runtime.GOOS { + case "darwin": + return filepath.Join(home, "Library", dirname) + case "windows": + // We used to put everything in %HOME%\AppData\Roaming, but this caused + // problems with non-typical setups. If this fallback location exists and + // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%. + fallback := filepath.Join(home, "AppData", "Roaming", dirname) + appdata := windowsAppData() + if appdata == "" || isNonEmptyDir(fallback) { + return fallback + } + return filepath.Join(appdata, dirname) + default: + if xdgDataDir := os.Getenv("XDG_DATA_HOME"); xdgDataDir != "" { + return filepath.Join(xdgDataDir, strings.ToLower(dirname)) + } + return filepath.Join(home, ".local/share", strings.ToLower(dirname)) + } + } + // As we cannot guess a stable location, return empty and handle later + return "" +} + +func windowsAppData() string { + v := os.Getenv("LOCALAPPDATA") + if v == "" { + // Windows XP and below don't have LocalAppData. Crash here because + // we don't support Windows XP and undefining the variable will cause + // other issues. + panic("environment variable LocalAppData is undefined") + } + return v +} + +func isNonEmptyDir(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return false + } + names, _ := f.Readdir(1) + f.Close() + return len(names) > 0 +} + +func homeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home + } + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" +} diff --git a/common/peer_set.go b/common/peer_set.go new file mode 100644 index 0000000..fa72141 --- /dev/null +++ b/common/peer_set.go @@ -0,0 +1,56 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package common + +import ( + "github.com/holiman/uint256" + "github.com/libp2p/go-libp2p/core/peer" + "time" +) + +type Peer struct { + IPeer + CurrentHeight *uint256.Int + AddTimer time.Time +} + +type PeerSet []Peer +type PeerMap map[peer.ID]Peer + +func (pm PeerMap) ToSlice() PeerSet { + peerSet := PeerSet{} + for k, _ := range pm { + peerSet = append(peerSet, pm[k]) + } + + return peerSet +} + +func (ps PeerSet) Len() int { + return len(ps) +} + +func (ps PeerSet) Less(i, j int) bool { + if ps[i].CurrentHeight.Cmp(ps[j].CurrentHeight) == 1 { + return true + } + return false +} + +func (ps PeerSet) Swap(i, j int) { + ps[i], ps[j] = ps[j], ps[i] +} diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go new file mode 100644 index 0000000..7d55686 --- /dev/null +++ b/common/prque/lazyqueue.go @@ -0,0 +1,196 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package prque + +import ( + "container/heap" + "github.com/astranetworld/ast/common/mclock" + "time" +) + +// LazyQueue is a priority queue data structure where priorities can change over +// time and are only evaluated on demand. +// Two callbacks are required: +// - priority evaluates the actual priority of an item +// - maxPriority gives an upper estimate for the priority in any moment between +// now and the given absolute time +// +// If the upper estimate is exceeded then Update should be called for that item. +// A global Refresh function should also be called periodically. +type LazyQueue struct { + clock mclock.Clock + // Items are stored in one of two internal queues ordered by estimated max + // priority until the next and the next-after-next refresh. Update and Refresh + // always places items in queue[1]. + queue [2]*sstack + popQueue *sstack + period time.Duration + maxUntil mclock.AbsTime + indexOffset int + setIndex SetIndexCallback + priority PriorityCallback + maxPriority MaxPriorityCallback + lastRefresh1, lastRefresh2 mclock.AbsTime +} + +type ( + PriorityCallback func(data interface{}) int64 // actual priority callback + MaxPriorityCallback func(data interface{}, until mclock.AbsTime) int64 // estimated maximum priority callback +) + +// NewLazyQueue creates a new lazy queue +func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPriority MaxPriorityCallback, clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue { + q := &LazyQueue{ + popQueue: newSstack(nil, false), + setIndex: setIndex, + priority: priority, + maxPriority: maxPriority, + clock: clock, + period: refreshPeriod, + lastRefresh1: clock.Now(), + lastRefresh2: clock.Now(), + } + q.Reset() + q.refresh(clock.Now()) + return q +} + +// Reset clears the contents of the queue +func (q *LazyQueue) Reset() { + q.queue[0] = newSstack(q.setIndex0, false) + q.queue[1] = newSstack(q.setIndex1, false) +} + +// Refresh performs queue re-evaluation if necessary +func (q *LazyQueue) Refresh() { + now := q.clock.Now() + for time.Duration(now-q.lastRefresh2) >= q.period*2 { + q.refresh(now) + q.lastRefresh2 = q.lastRefresh1 + q.lastRefresh1 = now + } +} + +// refresh re-evaluates items in the older queue and swaps the two queues +func (q *LazyQueue) refresh(now mclock.AbsTime) { + q.maxUntil = now + mclock.AbsTime(q.period) + for q.queue[0].Len() != 0 { + q.Push(heap.Pop(q.queue[0]).(*item).value) + } + q.queue[0], q.queue[1] = q.queue[1], q.queue[0] + q.indexOffset = 1 - q.indexOffset + q.maxUntil += mclock.AbsTime(q.period) +} + +// Push adds an item to the queue +func (q *LazyQueue) Push(data interface{}) { + heap.Push(q.queue[1], &item{data, q.maxPriority(data, q.maxUntil)}) +} + +// Update updates the upper priority estimate for the item with the given queue index +func (q *LazyQueue) Update(index int) { + q.Push(q.Remove(index)) +} + +// Pop removes and returns the item with the greatest actual priority +func (q *LazyQueue) Pop() (interface{}, int64) { + var ( + resData interface{} + resPri int64 + ) + q.MultiPop(func(data interface{}, priority int64) bool { + resData = data + resPri = priority + return false + }) + return resData, resPri +} + +// peekIndex returns the index of the internal queue where the item with the +// highest estimated priority is or -1 if both are empty +func (q *LazyQueue) peekIndex() int { + if q.queue[0].Len() != 0 { + if q.queue[1].Len() != 0 && q.queue[1].blocks[0][0].priority > q.queue[0].blocks[0][0].priority { + return 1 + } + return 0 + } + if q.queue[1].Len() != 0 { + return 1 + } + return -1 +} + +// MultiPop pops multiple items from the queue and is more efficient than calling +// Pop multiple times. Popped items are passed to the callback. MultiPop returns +// when the callback returns false or there are no more items to pop. +func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) { + nextIndex := q.peekIndex() + for nextIndex != -1 { + data := heap.Pop(q.queue[nextIndex]).(*item).value + heap.Push(q.popQueue, &item{data, q.priority(data)}) + nextIndex = q.peekIndex() + for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { + i := heap.Pop(q.popQueue).(*item) + if !callback(i.value, i.priority) { + for q.popQueue.Len() != 0 { + q.Push(heap.Pop(q.popQueue).(*item).value) + } + return + } + nextIndex = q.peekIndex() // re-check because callback is allowed to push items back + } + } +} + +// PopItem pops the item from the queue only, dropping the associated priority value. +func (q *LazyQueue) PopItem() interface{} { + i, _ := q.Pop() + return i +} + +// Remove removes removes the item with the given index. +func (q *LazyQueue) Remove(index int) interface{} { + if index < 0 { + return nil + } + return heap.Remove(q.queue[index&1^q.indexOffset], index>>1).(*item).value +} + +// Empty checks whether the priority queue is empty. +func (q *LazyQueue) Empty() bool { + return q.queue[0].Len() == 0 && q.queue[1].Len() == 0 +} + +// Size returns the number of items in the priority queue. +func (q *LazyQueue) Size() int { + return q.queue[0].Len() + q.queue[1].Len() +} + +// setIndex0 translates internal queue item index to the virtual index space of LazyQueue +func (q *LazyQueue) setIndex0(data interface{}, index int) { + if index == -1 { + q.setIndex(data, -1) + } else { + q.setIndex(data, index+index) + } +} + +// setIndex1 translates internal queue item index to the virtual index space of LazyQueue +func (q *LazyQueue) setIndex1(data interface{}, index int) { + q.setIndex(data, index+index+1) +} diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go new file mode 100644 index 0000000..acb7a3e --- /dev/null +++ b/common/prque/lazyqueue_test.go @@ -0,0 +1,123 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package prque + +import ( + "github.com/astranetworld/ast/common/mclock" + "math/rand" + "sync" + "testing" + "time" +) + +const ( + testItems = 1000 + testPriorityStep = 100 + testSteps = 1000000 + testStepPeriod = time.Millisecond + testQueueRefresh = time.Second + testAvgRate = float64(testPriorityStep) / float64(testItems) / float64(testStepPeriod) +) + +type lazyItem struct { + p, maxp int64 + last mclock.AbsTime + index int +} + +func testPriority(a interface{}) int64 { + return a.(*lazyItem).p +} + +func testMaxPriority(a interface{}, until mclock.AbsTime) int64 { + i := a.(*lazyItem) + dt := until - i.last + i.maxp = i.p + int64(float64(dt)*testAvgRate) + return i.maxp +} + +func testSetIndex(a interface{}, i int) { + a.(*lazyItem).index = i +} + +func TestLazyQueue(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + clock := &mclock.Simulated{} + q := NewLazyQueue(testSetIndex, testPriority, testMaxPriority, clock, testQueueRefresh) + + var ( + items [testItems]lazyItem + maxPri int64 + ) + + for i := range items[:] { + items[i].p = rand.Int63n(testPriorityStep * 10) + if items[i].p > maxPri { + maxPri = items[i].p + } + items[i].index = -1 + q.Push(&items[i]) + } + + var ( + lock sync.Mutex + wg sync.WaitGroup + stopCh = make(chan chan struct{}) + ) + defer wg.Wait() + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-clock.After(testQueueRefresh): + lock.Lock() + q.Refresh() + lock.Unlock() + case <-stopCh: + return + } + } + }() + + for c := 0; c < testSteps; c++ { + i := rand.Intn(testItems) + lock.Lock() + items[i].p += rand.Int63n(testPriorityStep*2-1) + 1 + if items[i].p > maxPri { + maxPri = items[i].p + } + items[i].last = clock.Now() + if items[i].p > items[i].maxp { + q.Update(items[i].index) + } + if rand.Intn(100) == 0 { + p := q.PopItem().(*lazyItem) + if p.p != maxPri { + lock.Unlock() + close(stopCh) + t.Fatalf("incorrect item (best known priority %d, popped %d)", maxPri, p.p) + } + q.Push(p) + } + lock.Unlock() + clock.Run(testStepPeriod) + clock.WaitForTimers(1) + } + + close(stopCh) +} diff --git a/common/prque/prque.go b/common/prque/prque.go new file mode 100755 index 0000000..54c78b5 --- /dev/null +++ b/common/prque/prque.go @@ -0,0 +1,83 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque". + +// Package prque implements a priority queue data structure supporting arbitrary +// value types and int64 priorities. +// +// If you would like to use a min-priority queue, simply negate the priorities. +// +// Internally the queue is based on the standard heap package working on a +// sortable version of the block based stack. +package prque + +import ( + "container/heap" +) + +// Priority queue data structure. +type Prque struct { + cont *sstack +} + +// New creates a new priority queue. +func New(setIndex SetIndexCallback) *Prque { + return &Prque{newSstack(setIndex, false)} +} + +// NewWrapAround creates a new priority queue with wrap-around priority handling. +func NewWrapAround(setIndex SetIndexCallback) *Prque { + return &Prque{newSstack(setIndex, true)} +} + +// Pushes a value with a given priority into the queue, expanding if necessary. +func (p *Prque) Push(data interface{}, priority int64) { + heap.Push(p.cont, &item{data, priority}) +} + +// Peek returns the value with the greates priority but does not pop it off. +func (p *Prque) Peek() (interface{}, int64) { + item := p.cont.blocks[0][0] + return item.value, item.priority +} + +// Pops the value with the greates priority off the stack and returns it. +// Currently no shrinking is done. +func (p *Prque) Pop() (interface{}, int64) { + item := heap.Pop(p.cont).(*item) + return item.value, item.priority +} + +// Pops only the item from the queue, dropping the associated priority value. +func (p *Prque) PopItem() interface{} { + return heap.Pop(p.cont).(*item).value +} + +// Remove removes the element with the given index. +func (p *Prque) Remove(i int) interface{} { + if i < 0 { + return nil + } + return heap.Remove(p.cont, i) +} + +// Checks whether the priority queue is empty. +func (p *Prque) Empty() bool { + return p.cont.Len() == 0 +} + +// Returns the number of element in the priority queue. +func (p *Prque) Size() int { + return p.cont.Len() +} + +// Clears the contents of the priority queue. +func (p *Prque) Reset() { + *p = *New(p.cont.setIndex) +} diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go new file mode 100644 index 0000000..1cffceb --- /dev/null +++ b/common/prque/prque_test.go @@ -0,0 +1,130 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "testing" +) + +func TestPrque(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out the elements in priority order and verify them + prevPrio := int64(size + 1) + for !queue.Empty() { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + } +} + +func TestReset(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out half the elements in priority order and verify them + prevPrio := int64(size + 1) + for i := 0; i < size/2; i++ { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + // Reset and ensure it's empty + queue.Reset() + if !queue.Empty() { + t.Errorf("priority queue not empty after reset: %v", queue) + } + } +} + +func BenchmarkPush(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + // Execute the benchmark + b.ResetTimer() + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } +} + +func BenchmarkPop(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } + // Execute the benchmark + b.ResetTimer() + for !queue.Empty() { + queue.Pop() + } +} diff --git a/common/prque/sstack.go b/common/prque/sstack.go new file mode 100755 index 0000000..b06a954 --- /dev/null +++ b/common/prque/sstack.go @@ -0,0 +1,120 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque". + +package prque + +// The size of a block of data +const blockSize = 4096 + +// A prioritized item in the sorted stack. +// +// Note: priorities can "wrap around" the int64 range, a comes before b if (a.priority - b.priority) > 0. +// The difference between the lowest and highest priorities in the queue at any point should be less than 2^63. +type item struct { + value interface{} + priority int64 +} + +// SetIndexCallback is called when the element is moved to a new index. +// Providing SetIndexCallback is optional, it is needed only if the application needs +// to delete elements other than the top one. +type SetIndexCallback func(data interface{}, index int) + +// Internal sortable stack data structure. Implements the Push and Pop ops for +// the stack (heap) functionality and the Len, Less and Swap methods for the +// sortability requirements of the heaps. +type sstack struct { + setIndex SetIndexCallback + size int + capacity int + offset int + wrapAround bool + + blocks [][]*item + active []*item +} + +// Creates a new, empty stack. +func newSstack(setIndex SetIndexCallback, wrapAround bool) *sstack { + result := new(sstack) + result.setIndex = setIndex + result.active = make([]*item, blockSize) + result.blocks = [][]*item{result.active} + result.capacity = blockSize + result.wrapAround = wrapAround + return result +} + +// Pushes a value onto the stack, expanding it if necessary. Required by +// heap.Interface. +func (s *sstack) Push(data interface{}) { + if s.size == s.capacity { + s.active = make([]*item, blockSize) + s.blocks = append(s.blocks, s.active) + s.capacity += blockSize + s.offset = 0 + } else if s.offset == blockSize { + s.active = s.blocks[s.size/blockSize] + s.offset = 0 + } + if s.setIndex != nil { + s.setIndex(data.(*item).value, s.size) + } + s.active[s.offset] = data.(*item) + s.offset++ + s.size++ +} + +// Pops a value off the stack and returns it. Currently no shrinking is done. +// Required by heap.Interface. +func (s *sstack) Pop() (res interface{}) { + s.size-- + s.offset-- + if s.offset < 0 { + s.offset = blockSize - 1 + s.active = s.blocks[s.size/blockSize] + } + res, s.active[s.offset] = s.active[s.offset], nil + if s.setIndex != nil { + s.setIndex(res.(*item).value, -1) + } + return +} + +// Returns the length of the stack. Required by sort.Interface. +func (s *sstack) Len() int { + return s.size +} + +// Compares the priority of two elements of the stack (higher is first). +// Required by sort.Interface. +func (s *sstack) Less(i, j int) bool { + a, b := s.blocks[i/blockSize][i%blockSize].priority, s.blocks[j/blockSize][j%blockSize].priority + if s.wrapAround { + return a-b > 0 + } + return a > b +} + +// Swaps two elements in the stack. Required by sort.Interface. +func (s *sstack) Swap(i, j int) { + ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize + a, b := s.blocks[jb][jo], s.blocks[ib][io] + if s.setIndex != nil { + s.setIndex(a.value, i) + s.setIndex(b.value, j) + } + s.blocks[ib][io], s.blocks[jb][jo] = a, b +} + +// Resets the stack, effectively clearing its contents. +func (s *sstack) Reset() { + *s = *newSstack(s.setIndex, false) +} diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go new file mode 100644 index 0000000..bc62989 --- /dev/null +++ b/common/prque/sstack_test.go @@ -0,0 +1,100 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "sort" + "testing" +) + +func TestSstack(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil, false) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + rest := []*item{} + for stack.Len() > 0 { + rest = append(rest, stack.Pop().(*item)) + } + // Make sure the contents of the resulting slices are ok + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + if i%2 == 1 && data[i] != rest[len(rest)-i/2-1] { + t.Errorf("push/pop mismatch: have %v, want %v.", rest[len(rest)-i/2-1], data[i]) + } + } + } +} + +func TestSstackSort(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), int64(i)} + } + // Push all the data into the stack + stack := newSstack(nil, false) + for _, val := range data { + stack.Push(val) + } + // Sort and pop the stack contents (should reverse the order) + sort.Sort(stack) + for _, val := range data { + out := stack.Pop() + if out != val { + t.Errorf("push/pop mismatch after sort: have %v, want %v.", out, val) + } + } +} + +func TestSstackReset(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil, false) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + // Reset and verify both pulled and stack contents + stack.Reset() + if stack.Len() != 0 { + t.Errorf("stack not empty after reset: %v", stack) + } + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + } + } +} diff --git a/common/transaction/access_list.go b/common/transaction/access_list.go new file mode 100644 index 0000000..b001639 --- /dev/null +++ b/common/transaction/access_list.go @@ -0,0 +1,131 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "github.com/astranetworld/ast/common/hash" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" +) + +type AccessList []AccessTuple + +// AccessTuple is the element type of an access list. +type AccessTuple struct { + Address types.Address `json:"address" gencodec:"required"` + StorageKeys []types.Hash `json:"storageKeys" gencodec:"required"` +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al AccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + return sum +} + +type AccessListTx struct { + ChainID *uint256.Int // destination chain ID + Nonce uint64 // nonce of sender account + GasPrice *uint256.Int // wei per gas + Gas uint64 // gas limit + To *types.Address `rlp:"nil"` // nil means contract creation + From *types.Address `rlp:"nil"` // nil means contract creation + Value *uint256.Int // wei amount + Data []byte // contract invocation input data + AccessList AccessList // EIP-2930 access list + Sign []byte // signature values + V, R, S *uint256.Int // signature values +} + +func (tx *AccessListTx) copy() TxData { + cpy := &AccessListTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + From: copyAddressPtr(tx.From), + Data: types.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(uint256.Int), + ChainID: new(uint256.Int), + GasPrice: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + + if tx.Sign != nil { + copy(cpy.Sign, tx.Sign) + } + return cpy +} + +// accessors for innerTx. +func (tx *AccessListTx) txType() byte { return AccessListTxType } +func (tx *AccessListTx) chainID() *uint256.Int { return tx.ChainID } +func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } +func (tx *AccessListTx) data() []byte { return tx.Data } +func (tx *AccessListTx) gas() uint64 { return tx.Gas } +func (tx *AccessListTx) gasPrice() *uint256.Int { return tx.GasPrice } +func (tx *AccessListTx) gasTipCap() *uint256.Int { return tx.GasPrice } +func (tx *AccessListTx) gasFeeCap() *uint256.Int { return tx.GasPrice } +func (tx *AccessListTx) value() *uint256.Int { return tx.Value } +func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } +func (tx *AccessListTx) to() *types.Address { return tx.To } +func (tx *AccessListTx) from() *types.Address { return tx.From } +func (tx *AccessListTx) sign() []byte { return tx.Sign } + +func (tx *AccessListTx) hash() types.Hash { + hash := hash.PrefixedRlpHash(AccessListTxType, []interface{}{ + tx.ChainID, + tx.Nonce, + tx.GasPrice, + tx.Gas, + tx.To, + tx.Value, + tx.Data, + tx.AccessList, + tx.V, tx.R, tx.S, + }) + return hash +} + +func (tx *AccessListTx) rawSignatureValues() (v, r, s *uint256.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *uint256.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} diff --git a/common/transaction/dynamic_fee_tx.go b/common/transaction/dynamic_fee_tx.go new file mode 100644 index 0000000..c77fbde --- /dev/null +++ b/common/transaction/dynamic_fee_tx.go @@ -0,0 +1,125 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "github.com/astranetworld/ast/common/hash" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/internal/avm/common" + "github.com/holiman/uint256" +) + +type DynamicFeeTx struct { + ChainID *uint256.Int + Nonce uint64 + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + Gas uint64 + To *types.Address `rlp:"nil"` // nil means contract creation + From *types.Address `rlp:"nil"` // nil means contract creation + Value *uint256.Int + Data []byte + AccessList AccessList + Sign []byte // Signature values + V, R, S *uint256.Int // signature values +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *DynamicFeeTx) copy() TxData { + cpy := &DynamicFeeTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + From: copyAddressPtr(tx.From), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(uint256.Int), + ChainID: new(uint256.Int), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + if tx.Sign != nil { + copy(cpy.Sign, tx.Sign) + } + return cpy +} + +// accessors for innerTx. +func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType } +func (tx *DynamicFeeTx) chainID() *uint256.Int { return tx.ChainID } +func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList } +func (tx *DynamicFeeTx) data() []byte { return tx.Data } +func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas } +func (tx *DynamicFeeTx) gasFeeCap() *uint256.Int { return tx.GasFeeCap } +func (tx *DynamicFeeTx) gasTipCap() *uint256.Int { return tx.GasTipCap } +func (tx *DynamicFeeTx) gasPrice() *uint256.Int { return tx.GasFeeCap } +func (tx *DynamicFeeTx) value() *uint256.Int { return tx.Value } +func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } +func (tx *DynamicFeeTx) to() *types.Address { return tx.To } +func (tx *DynamicFeeTx) from() *types.Address { return tx.From } +func (tx *DynamicFeeTx) sign() []byte { return tx.Sign } + +// Hash computes the hash (but not for signatures!) +func (tx *DynamicFeeTx) hash() types.Hash { + hash := hash.PrefixedRlpHash(DynamicFeeTxType, []interface{}{ + tx.ChainID, + tx.Nonce, + tx.GasTipCap, + tx.GasFeeCap, + tx.Gas, + tx.To, + tx.Value, + tx.Data, + tx.AccessList, + tx.V, tx.R, tx.S, + }) + return hash +} + +func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *uint256.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *uint256.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} diff --git a/common/transaction/legacy_tx.go b/common/transaction/legacy_tx.go new file mode 100644 index 0000000..5f16e77 --- /dev/null +++ b/common/transaction/legacy_tx.go @@ -0,0 +1,137 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "github.com/astranetworld/ast/common/hash" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" +) + +// LegacyTx is the transaction data of regular Ethereum transactions. +type LegacyTx struct { + Nonce uint64 // nonce of sender account + GasPrice *uint256.Int // wei per gas + Gas uint64 // gas limit + To *types.Address `rlp:"nil"` // nil means contract creation + From *types.Address `rlp:"nil"` // nil means contract creation + Value *uint256.Int // wei amount + Data []byte // contract invocation input data + V, R, S *uint256.Int // signature values + Sign []byte +} + +// NewTransaction creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewTransaction(nonce uint64, from types.Address, to *types.Address, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + To: to, + From: &from, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// NewContractCreation creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewContractCreation(nonce uint64, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *LegacyTx) copy() TxData { + cpy := &LegacyTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + From: copyAddressPtr(tx.From), + Data: types.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are initialized below. + Value: new(uint256.Int), + GasPrice: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + if tx.sign() != nil { + copy(cpy.Sign, tx.Sign) + } + + return cpy +} + +// accessors for innerTx. +func (tx *LegacyTx) txType() byte { return LegacyTxType } +func (tx *LegacyTx) chainID() *uint256.Int { + return DeriveChainId(tx.V) +} +func (tx *LegacyTx) accessList() AccessList { return nil } +func (tx *LegacyTx) data() []byte { return tx.Data } +func (tx *LegacyTx) gas() uint64 { return tx.Gas } +func (tx *LegacyTx) gasPrice() *uint256.Int { return tx.GasPrice } +func (tx *LegacyTx) gasTipCap() *uint256.Int { return tx.GasPrice } +func (tx *LegacyTx) gasFeeCap() *uint256.Int { return tx.GasPrice } +func (tx *LegacyTx) value() *uint256.Int { return tx.Value } +func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } +func (tx *LegacyTx) to() *types.Address { return tx.To } +func (tx *LegacyTx) from() *types.Address { return tx.From } +func (tx *LegacyTx) sign() []byte { return tx.Sign } + +func (tx *LegacyTx) rawSignatureValues() (v, r, s *uint256.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *uint256.Int) { + tx.V, tx.R, tx.S = v, r, s +} + +func (tx *LegacyTx) hash() types.Hash { + hash := hash.RlpHash([]interface{}{ + tx.Nonce, + tx.GasPrice, + tx.Gas, + tx.To, + tx.Value, + tx.Data, + tx.V, tx.R, tx.S, + }) + return hash +} diff --git a/common/transaction/sakuragi_tx.go b/common/transaction/sakuragi_tx.go new file mode 100644 index 0000000..63a443c --- /dev/null +++ b/common/transaction/sakuragi_tx.go @@ -0,0 +1,33 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" +) + +type SakuragiTx struct { + Nonce uint64 // nonce of sender account + GasPrice uint256.Int // wei per gas + Gas uint64 // gas limit + To *types.Address + From *types.Address + Value uint256.Int // wei amount + Data []byte // contract invocation input data + Sign []byte // signature values +} diff --git a/common/transaction/transaction.go b/common/transaction/transaction.go new file mode 100644 index 0000000..f714bc8 --- /dev/null +++ b/common/transaction/transaction.go @@ -0,0 +1,695 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "bytes" + "fmt" + "github.com/astranetworld/ast/utils" + "github.com/holiman/uint256" + "google.golang.org/protobuf/proto" + "math/big" + "sync/atomic" + "time" + + "github.com/astranetworld/ast/api/protocol/types_pb" + "github.com/astranetworld/ast/common/types" +) + +var ( + ErrGasFeeCapTooLow = fmt.Errorf("fee cap less than base fee") + ErrUnmarshalHash = fmt.Errorf("hash verify falied") +) + +// Transaction types. +const ( + LegacyTxType = iota + AccessListTxType + DynamicFeeTxType +) + +type TxData interface { + txType() byte // returns the type ID + copy() TxData // creates a deep copy and initializes all fields + + chainID() *uint256.Int + accessList() AccessList + data() []byte + gas() uint64 + gasPrice() *uint256.Int + gasTipCap() *uint256.Int + gasFeeCap() *uint256.Int + value() *uint256.Int + nonce() uint64 + to() *types.Address + from() *types.Address + sign() []byte + hash() types.Hash + + rawSignatureValues() (v, r, s *uint256.Int) + setSignatureValues(chainID, v, r, s *uint256.Int) + + //Marshal() ([]byte, error) + //MarshalTo(data []byte) (n int, err error) + //Unmarshal(data []byte) error + //Size() int +} + +// Transactions implements DerivableList for transactions. +type Transactions []*Transaction + +// Len returns the length of s. +func (s Transactions) Len() int { return len(s) } + +// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors +// because we assume that *Transaction will only ever contain valid txs that were either +// constructed by decoding or via public API in this package. +func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) { + tx := s[i] + proto, _ := tx.Marshal() + w.Write(proto) +} + +type Transaction struct { + inner TxData // Consensus contents of a transaction + time time.Time // Time first seen locally (spam avoidance) + + // caches + hash atomic.Value + size atomic.Value + from atomic.Value +} + +// NewTx creates a new transaction. +func NewTx(inner TxData) *Transaction { + tx := new(Transaction) + tx.setDecoded(inner.copy(), 0) + return tx +} + +func txDataFromProtoMessage(message proto.Message) (TxData, error) { + var ( + pbTx *types_pb.Transaction + ok bool + inner TxData + ) + + if pbTx, ok = message.(*types_pb.Transaction); !ok { + return nil, fmt.Errorf("aa") + } + + switch pbTx.Type { + case LegacyTxType: + var itx LegacyTx + if nil != pbTx.To { + itx.To = utils.ConvertH160ToPAddress(pbTx.To) + if *itx.To == (types.Address{}) { + itx.To = nil + } + } + itx.From = utils.ConvertH160ToPAddress(pbTx.From) + itx.Sign = pbTx.Sign + itx.Nonce = pbTx.Nonce + itx.Gas = pbTx.Gas + itx.GasPrice = utils.ConvertH256ToUint256Int(pbTx.GasPrice) + itx.Value = utils.ConvertH256ToUint256Int(pbTx.Value) + if nil != pbTx.V { + itx.V = utils.ConvertH256ToUint256Int(pbTx.V) + } + if nil != pbTx.R { + itx.R = utils.ConvertH256ToUint256Int(pbTx.R) + } + if nil != pbTx.S { + itx.S = utils.ConvertH256ToUint256Int(pbTx.S) + } + itx.Data = pbTx.Data + inner = &itx + case AccessListTxType: + var altt AccessListTx + altt.ChainID = uint256.NewInt(pbTx.ChainID) + altt.Nonce = pbTx.Nonce + altt.Gas = pbTx.Gas + altt.GasPrice = utils.ConvertH256ToUint256Int(pbTx.GasPrice) + altt.Value = utils.ConvertH256ToUint256Int(pbTx.Value) + if nil != pbTx.V { + altt.V = utils.ConvertH256ToUint256Int(pbTx.V) + } + if nil != pbTx.R { + altt.R = utils.ConvertH256ToUint256Int(pbTx.R) + } + if nil != pbTx.S { + altt.S = utils.ConvertH256ToUint256Int(pbTx.S) + } + altt.Data = pbTx.Data + if nil != pbTx.To { + altt.To = utils.ConvertH160ToPAddress(pbTx.To) + if *altt.To == (types.Address{}) { + altt.To = nil + } + } + //altt.To = utils.ConvertH160ToPAddress(pbTx.To) + altt.From = utils.ConvertH160ToPAddress(pbTx.From) + altt.Sign = pbTx.Sign + inner = &altt + case DynamicFeeTxType: + var dftt DynamicFeeTx + dftt.ChainID = uint256.NewInt(pbTx.ChainID) + dftt.Nonce = pbTx.Nonce + dftt.Gas = pbTx.Gas + dftt.GasFeeCap = utils.ConvertH256ToUint256Int(pbTx.FeePerGas) + dftt.GasTipCap = utils.ConvertH256ToUint256Int(pbTx.PriorityFeePerGas) + dftt.Value = utils.ConvertH256ToUint256Int(pbTx.Value) + if nil != pbTx.V { + dftt.V = utils.ConvertH256ToUint256Int(pbTx.V) + } + if nil != pbTx.R { + dftt.R = utils.ConvertH256ToUint256Int(pbTx.R) + } + if nil != pbTx.S { + dftt.S = utils.ConvertH256ToUint256Int(pbTx.S) + } + dftt.Data = pbTx.Data + if nil != pbTx.To { + dftt.To = utils.ConvertH160ToPAddress(pbTx.To) + if *dftt.To == (types.Address{}) { + dftt.To = nil + } + } + + //dftt.To = utils.ConvertH160ToPAddress(pbTx.To) + dftt.From = utils.ConvertH160ToPAddress(pbTx.From) + dftt.Sign = pbTx.Sign + inner = &dftt + } + + // todo + protoHash := utils.ConvertH256ToHash(pbTx.Hash) + innerHash := inner.hash() + if bytes.Compare(protoHash[:], innerHash[:]) != 0 { + //return nil, ErrUnmarshalHash + } + + return inner, nil +} + +func FromProtoMessage(message proto.Message) (*Transaction, error) { + inner, err := txDataFromProtoMessage(message) + if err != nil { + return nil, err + } + return NewTx(inner), nil +} + +func (tx *Transaction) ToProtoMessage() proto.Message { + var pbTx types_pb.Transaction + pbTx.Type = uint64(tx.inner.txType()) + + switch t := tx.inner.(type) { + case *AccessListTx: + pbTx.ChainID = t.ChainID.Uint64() + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + case *LegacyTx: + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + //pbTx.To = utils.ConvertAddressToH160(*tx.To()) + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + case *DynamicFeeTx: + pbTx.ChainID = t.ChainID.Uint64() + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + //pbTx.To = utils.ConvertAddressToH160(*tx.To()) + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + pbTx.FeePerGas = utils.ConvertUint256IntToH256(t.GasFeeCap) + pbTx.PriorityFeePerGas = utils.ConvertUint256IntToH256(t.GasTipCap) + } + if tx.To() != nil { + pbTx.To = utils.ConvertAddressToH160(*tx.To()) + } + + pbTx.Hash = utils.ConvertHashToH256(tx.Hash()) + + v, r, s := tx.RawSignatureValues() + if nil != v { + pbTx.V = utils.ConvertUint256IntToH256(v) + } + if nil != r { + pbTx.R = utils.ConvertUint256IntToH256(r) + } + if nil != s { + pbTx.S = utils.ConvertUint256IntToH256(s) + } + + return &pbTx +} + +func (tx *Transaction) setDecoded(inner TxData, size int) { + tx.inner = inner + tx.time = time.Now() + //if size > 0 { + // tx.size.Store(common.StorageSize(size)) + //} +} + +func (tx *Transaction) RawSignatureValues() (v, r, s *uint256.Int) { + return tx.inner.rawSignatureValues() +} + +// WithSignature todo +func (tx *Transaction) WithSignatureValues(v, r, s *uint256.Int) (*Transaction, error) { + //todo + tx.inner.setSignatureValues(uint256.NewInt(100100100), v, r, s) + return tx, nil +} + +func (tx Transaction) Marshal() ([]byte, error) { + var pbTx types_pb.Transaction + pbTx.Type = uint64(tx.inner.txType()) + + switch t := tx.inner.(type) { + case *AccessListTx: + pbTx.ChainID = t.ChainID.Uint64() + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + case *LegacyTx: + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + //pbTx.To = utils.ConvertAddressToH160(*tx.To()) + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + case *DynamicFeeTx: + pbTx.ChainID = t.ChainID.Uint64() + pbTx.Nonce = tx.Nonce() + pbTx.Gas = tx.Gas() + pbTx.GasPrice = utils.ConvertUint256IntToH256(tx.GasPrice()) + pbTx.Value = utils.ConvertUint256IntToH256(tx.Value()) + pbTx.Data = tx.Data() + //pbTx.To = utils.ConvertAddressToH160(*tx.To()) + pbTx.From = utils.ConvertAddressToH160(*tx.From()) + pbTx.Sign = t.Sign + pbTx.FeePerGas = utils.ConvertUint256IntToH256(t.GasFeeCap) + pbTx.PriorityFeePerGas = utils.ConvertUint256IntToH256(t.GasTipCap) + } + if tx.To() != nil { + pbTx.To = utils.ConvertAddressToH160(*tx.To()) + } + v, r, s := tx.RawSignatureValues() + if nil != v { + pbTx.V = utils.ConvertUint256IntToH256(v) + } + if nil != r { + pbTx.R = utils.ConvertUint256IntToH256(r) + } + if nil != s { + pbTx.S = utils.ConvertUint256IntToH256(s) + } + return proto.Marshal(&pbTx) +} + +func (tx *Transaction) MarshalTo(data []byte) (n int, err error) { + b, err := tx.Marshal() + if err != nil { + return 0, err + } + + copy(data, b) + return len(b), nil +} + +func (tx *Transaction) Unmarshal(data []byte) error { + var pbTx types_pb.Transaction + var err error + var inner TxData + err = proto.Unmarshal(data, &pbTx) + if err != nil { + return err + } + + inner, err = txDataFromProtoMessage(&pbTx) + if err != nil { + return err + } + + tx.setDecoded(inner, 0) + return err +} + +func (tx *Transaction) Type() uint8 { + return tx.inner.txType() +} + +func (tx *Transaction) ChainId() *uint256.Int { + return tx.inner.chainID() +} + +func (tx *Transaction) Data() []byte { + return tx.inner.data() +} + +func (tx *Transaction) AccessList() AccessList { + return tx.inner.accessList() +} + +func (tx *Transaction) Gas() uint64 { + return tx.inner.gas() +} + +func (tx *Transaction) GasPrice() *uint256.Int { + return tx.inner.gasPrice() +} + +func (tx *Transaction) GasTipCap() *uint256.Int { + return tx.inner.gasTipCap() +} + +func (tx *Transaction) GasFeeCap() *uint256.Int { + return tx.inner.gasFeeCap() +} + +func (tx *Transaction) Value() *uint256.Int { + return tx.inner.value() +} + +func (tx *Transaction) Nonce() uint64 { + return tx.inner.nonce() +} + +func (tx *Transaction) To() *types.Address { + return copyAddressPtr(tx.inner.to()) +} + +func (tx *Transaction) From() *types.Address { + return tx.inner.from() +} + +func (tx *Transaction) SetFrom(addr types.Address) { + switch t := tx.inner.(type) { + case *AccessListTx: + t.From = &addr + case *LegacyTx: + t.From = &addr + case *DynamicFeeTx: + t.From = &addr + } +} + +func (tx *Transaction) SetNonce(nonce uint64) { + switch t := tx.inner.(type) { + case *AccessListTx: + t.Nonce = nonce + case *LegacyTx: + t.Nonce = nonce + case *DynamicFeeTx: + t.Nonce = nonce + } +} + +func (tx *Transaction) Sign() []byte { + return tx.inner.sign() +} + +func (tx *Transaction) Cost() *uint256.Int { + price := tx.inner.gasPrice() + gas := uint256.NewInt(tx.inner.gas()) + total := new(uint256.Int).Mul(price, gas) + total = total.Add(total, tx.Value()) + return total +} + +func (tx *Transaction) Hash() types.Hash { + if hash := tx.hash.Load(); hash != nil { + return hash.(types.Hash) + } + h := tx.inner.hash() + tx.hash.Store(h) + return h +} + +// GasFeeCapCmp compares the fee cap of two transactions. +func (tx *Transaction) GasFeeCapCmp(other *Transaction) int { + return tx.inner.gasFeeCap().Cmp(other.inner.gasFeeCap()) +} + +// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. +func (tx *Transaction) EffectiveGasTipIntCmp(other *uint256.Int, baseFee *uint256.Int) int { + if baseFee == nil { + return tx.GasTipCapIntCmp(other) + } + return tx.EffectiveGasTipValue(baseFee).Cmp(other) +} + +// GasTipCapCmp compares the gasTipCap of two transactions. +func (tx *Transaction) GasTipCapCmp(other *Transaction) int { + return tx.inner.gasTipCap().Cmp(other.inner.gasTipCap()) +} + +// GasFeeCapIntCmp compares the fee cap of the transaction against the given fee cap. +func (tx *Transaction) GasFeeCapIntCmp(other *uint256.Int) int { + return tx.inner.gasFeeCap().Cmp(other) +} + +// GasTipCapIntCmp compares the gasTipCap of the transaction against the given gasTipCap. +func (tx *Transaction) GasTipCapIntCmp(other *uint256.Int) int { + return tx.inner.gasTipCap().Cmp(other) +} + +// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an +// error in case the effective gasTipCap is negative +func (tx *Transaction) EffectiveGasTipValue(baseFee *uint256.Int) *uint256.Int { + effectiveTip, _ := tx.EffectiveGasTip(baseFee) + return effectiveTip +} + +// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee. +func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int { + if baseFee == nil { + return tx.GasTipCapCmp(other) + } + return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee)) +} + +// EffectiveGasTip returns the effective miner gasTipCap for the given base fee. +// Note: if the effective gasTipCap is negative, this method returns both error +// the actual negative value, _and_ ErrGasFeeCapTooLow +func (tx *Transaction) EffectiveGasTip(baseFee *uint256.Int) (*uint256.Int, error) { + if baseFee == nil { + return tx.GasTipCap(), nil + } + var err error + gasFeeCap := tx.GasFeeCap() + if gasFeeCap.Cmp(baseFee) == -1 { + err = ErrGasFeeCapTooLow + } + return uint256Min(tx.GasTipCap(), new(uint256.Int).Sub(gasFeeCap, baseFee)), err +} + +func uint256Min(x, y *uint256.Int) *uint256.Int { + if x.Cmp(y) == 1 { + return x + } + return y +} + +func isProtectedV(V *big.Int) bool { + if V.BitLen() <= 8 { + v := V.Uint64() + return v != 27 && v != 28 && v != 1 && v != 0 + } + // anything not 27 or 28 is considered protected + return true +} + +// Protected says whether the transaction is replay-protected. +func (tx *Transaction) Protected() bool { + switch tx := tx.inner.(type) { + case *LegacyTx: + return tx.V.ToBig() != nil && isProtectedV(tx.V.ToBig()) + default: + return true + } +} + +// WithSignature returns a new transaction with the given signature. +// This signature needs to be in the [R || S || V] format where V is 0 or 1. +func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { + r, s, v, err := signer.SignatureValues(tx, sig) + if err != nil { + return nil, err + } + cpy := tx.inner.copy() + var chainID *uint256.Int + if signer.ChainID() == nil { + chainID = nil + } else { + chainID, _ = uint256.FromBig(signer.ChainID()) + } + + v1, _ := uint256.FromBig(v) + r1, _ := uint256.FromBig(r) + s1, _ := uint256.FromBig(s) + cpy.setSignatureValues(chainID, v1, r1, s1) + return &Transaction{inner: cpy, time: tx.time}, nil +} + +//type Transaction struct { +// to types.Address +// from types.Address +// nonce uint64 +// amount uint256.Int +// gasLimit uint64 +// gasPrice uint256.Int +// gasFeeCap uint256.Int +// payload []byte +//} + +//func (t *Transaction) ToProtoMessage() proto.Message { +// pbTx := types_pb.Transaction{ +// From: types.Address(t.from), +// To: types.Address(t.to), +// Value: t.amount, +// Data: t.payload, +// Nonce: t.nonce, +// S: nil, +// V: nil, +// R: nil, +// } +// +// return &pbTx +//} + +func copyAddressPtr(a *types.Address) *types.Address { + if a == nil { + return nil + } + cpy := *a + return &cpy +} + +// Message is a fully derived transaction and implements core.Message +type Message struct { + to *types.Address + from types.Address + nonce uint64 + amount uint256.Int + gasLimit uint64 + gasPrice uint256.Int + feeCap uint256.Int + tip uint256.Int + data []byte + accessList AccessList + checkNonce bool + isFree bool +} + +func NewMessage(from types.Address, to *types.Address, nonce uint64, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, feeCap, tip *uint256.Int, data []byte, accessList AccessList, checkNonce bool, isFree bool) Message { + m := Message{ + from: from, + to: to, + nonce: nonce, + amount: *amount, + gasLimit: gasLimit, + data: data, + accessList: accessList, + checkNonce: checkNonce, + isFree: isFree, + } + if gasPrice != nil { + m.gasPrice.Set(gasPrice) + } + if tip != nil { + m.tip.Set(tip) + } + if feeCap != nil { + m.feeCap.Set(feeCap) + } + return m +} + +// AsMessage returns the transaction as a core.Message. +func (tx *Transaction) AsMessage(s Signer, baseFee *uint256.Int) (Message, error) { + msg := Message{ + nonce: tx.Nonce(), + gasLimit: tx.Gas(), + gasPrice: *new(uint256.Int).Set(tx.GasPrice()), + feeCap: *new(uint256.Int).Set(tx.GasFeeCap()), + tip: *new(uint256.Int).Set(tx.GasTipCap()), + //gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + //gasTipCap: new(big.Int).Set(tx.GasTipCap()), + to: tx.To(), + amount: *tx.Value(), + data: tx.Data(), + accessList: tx.AccessList(), + checkNonce: false, + //isFake: false, + } + + // If baseFee provided, set gasPrice to effectiveGasPrice. + if baseFee != nil { + msg.gasPrice.Add(&msg.tip, baseFee) + + if msg.gasPrice.Gt(&msg.feeCap) { + msg.gasPrice = msg.feeCap + } + } + //var err error + //msg.from, err = Sender(s, tx) + //if nil != err { + // return msg, err + //} + msg.from = *tx.From() + + return msg, nil +} +func (m Message) From() types.Address { return m.from } +func (m Message) To() *types.Address { return m.to } +func (m Message) GasPrice() *uint256.Int { return &m.gasPrice } +func (m Message) FeeCap() *uint256.Int { return &m.feeCap } +func (m Message) Tip() *uint256.Int { return &m.tip } +func (m Message) Value() *uint256.Int { return &m.amount } +func (m Message) Gas() uint64 { return m.gasLimit } +func (m Message) Nonce() uint64 { return m.nonce } +func (m Message) Data() []byte { return m.data } +func (m Message) AccessList() AccessList { return m.accessList } +func (m Message) CheckNonce() bool { return m.checkNonce } +func (m *Message) SetCheckNonce(checkNonce bool) { + m.checkNonce = checkNonce +} +func (m Message) IsFree() bool { return m.isFree } +func (m *Message) SetIsFree(isFree bool) { + m.isFree = isFree +} diff --git a/common/transaction/transaction_signing.go b/common/transaction/transaction_signing.go new file mode 100644 index 0000000..93ef3ef --- /dev/null +++ b/common/transaction/transaction_signing.go @@ -0,0 +1,506 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/hash" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/common/u256" + "github.com/astranetworld/ast/params" + "github.com/holiman/uint256" + "math/big" +) + +var ( + ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrTxTypeNotSupported = errors.New("transaction type not supported") + ErrInvalidChainId = errors.New("invalid chain id for signer") +) + +// sigCache is used to cache the derived sender and contains +// the signer used to derive it. +type sigCache struct { + signer Signer + from types.Address +} + +// MakeSigner returns a Signer based on the given chain config and block number. +func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { + var signer Signer + switch { + case config.IsLondon(blockNumber.Uint64()): + signer = NewLondonSigner(config.ChainID) + case config.IsBerlin(blockNumber.Uint64()): + signer = NewEIP2930Signer(config.ChainID) + case config.IsEip1559FeeCollector(blockNumber.Uint64()): + signer = NewEIP155Signer(config.ChainID) + case config.IsHomestead(blockNumber.Uint64()): + signer = HomesteadSigner{} + default: + signer = FrontierSigner{} + } + return signer +} + +// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically, +// this enables support for EIP-155 replay protection and all implemented EIP-2718 +// transaction types if chainID is non-nil. +// +// Use this in transaction-handling code where the current block number and fork +// configuration are unknown. If you have a ChainConfig, use LatestSigner instead. +// If you have a ChainConfig and know the current block number, use MakeSigner instead. +func LatestSignerForChainID(chainID *big.Int) Signer { + if chainID == nil { + return HomesteadSigner{} + } + return NewLondonSigner(chainID) +} + +// SignNewTx creates a transaction and signs it. +func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) { + tx := NewTx(txdata) + h := s.Hash(tx) + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return tx.WithSignature(s, sig) +} + +// SignTx signs the transaction using the given signer and private key. +func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) { + h := s.Hash(tx) + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return tx.WithSignature(s, sig) +} + +// Sender returns the address derived from the signature (V, R, S) using secp256k1 +// elliptic curve and an error if it failed deriving or upon an incorrect +// signature. +// +// Sender may cache the address, allowing it to be used regardless of +// signing method. The cache is invalidated if the cached signer does +// not match the signer used in the current call. +func Sender(signer Signer, tx *Transaction) (types.Address, error) { + if sc := tx.from.Load(); sc != nil { + sigCache := sc.(sigCache) + // If the signer used to derive from in a previous + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + addr, err := signer.Sender(tx) + if err != nil { + return types.Address{}, err + } + tx.from.Store(sigCache{signer: signer, from: addr}) + return addr, nil +} + +// Signer encapsulates transaction signature handling. The name of this type is slightly +// misleading because Signers don't actually sign, they're just for validating and +// processing of signatures. +// +// Note that this interface is not a stable API and may change at any time to accommodate +// new protocol rules. +type Signer interface { + // Sender returns the sender address of the transaction. + Sender(tx *Transaction) (types.Address, error) + + // SignatureValues returns the raw R, S, V values corresponding to the + // given signature. + SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) + ChainID() *big.Int + + // Hash returns 'signature hash', i.e. the transaction hash that is signed by the + // private key. This hash does not uniquely identify the transaction. + Hash(tx *Transaction) types.Hash + + // Equal returns true if the given signer is the same as the receiver. + Equal(Signer) bool +} + +type londonSigner struct{ eip2930Signer } + +// NewLondonSigner returns a signer that accepts +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewLondonSigner(chainId *big.Int) Signer { + return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} +} + +func (s londonSigner) Sender(tx *Transaction) (types.Address, error) { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Sender(tx) + } + V, R, S := tx.RawSignatureValues() + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V1 := new(big.Int).Add(V.ToBig(), big.NewInt(27)) + chainId, _ := uint256.FromBig(s.chainId) + id := tx.ChainId() + if id.Cmp(chainId) != 0 { + return types.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R.ToBig(), S.ToBig(), V1, true) +} + +func (s londonSigner) Equal(s2 Signer) bool { + x, ok := s2.(londonSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*DynamicFeeTx) + if !ok { + return s.eip2930Signer.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + chainId, _ := uint256.FromBig(s.chainId) + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s londonSigner) Hash(tx *Transaction) types.Hash { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Hash(tx) + } + return hash.PrefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) +} + +type eip2930Signer struct{ EIP155Signer } + +// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, +// EIP-155 replay protected transactions, and legacy Homestead transactions. +func NewEIP2930Signer(chainId *big.Int) Signer { + return eip2930Signer{NewEIP155Signer(chainId)} +} + +func (s eip2930Signer) ChainID() *big.Int { + return s.chainId +} + +func (s eip2930Signer) Equal(s2 Signer) bool { + x, ok := s2.(eip2930Signer) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s eip2930Signer) Sender(tx *Transaction) (types.Address, error) { + V, R, S := tx.RawSignatureValues() + V1 := V.ToBig() + switch tx.Type() { + case LegacyTxType: + if !tx.Protected() { + return HomesteadSigner{}.Sender(tx) + } + V1 = new(big.Int).Sub(V1, s.chainIdMul) + V1.Sub(V1, big8) + case AccessListTxType: + // AL txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V1 = new(big.Int).Add(V1, big.NewInt(27)) + default: + return types.Address{}, ErrTxTypeNotSupported + } + chainId, _ := uint256.FromBig(s.chainId) + id := tx.ChainId() + if id.Cmp(chainId) != 0 { + return types.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R.ToBig(), S.ToBig(), V1, true) +} + +func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + switch txdata := tx.inner.(type) { + case *LegacyTx: + return s.EIP155Signer.SignatureValues(tx, sig) + case *AccessListTx: + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + chainId, _ := uint256.FromBig(s.chainId) + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + default: + return nil, nil, nil, ErrTxTypeNotSupported + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s eip2930Signer) Hash(tx *Transaction) types.Hash { + switch tx.Type() { + case LegacyTxType: + return hash.RlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + s.chainId, uint(0), uint(0), + }) + case AccessListTxType: + return hash.PrefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) + default: + // This _should_ not happen, but in case someone sends in a bad + // json struct via RPC, it's probably more prudent to return an + // empty hash instead of killing the node with a panic + //panic("Unsupported transaction type: %d", tx.typ) + return types.Hash{} + } +} + +// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which +// are replay-protected as well as unprotected homestead transactions. +type EIP155Signer struct { + chainId, chainIdMul *big.Int +} + +func NewEIP155Signer(chainId *big.Int) EIP155Signer { + if chainId == nil { + chainId = new(big.Int) + } + return EIP155Signer{ + chainId: chainId, + chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)), + } +} + +func (s EIP155Signer) ChainID() *big.Int { + return s.chainId +} + +func (s EIP155Signer) Equal(s2 Signer) bool { + eip155, ok := s2.(EIP155Signer) + return ok && eip155.chainId.Cmp(s.chainId) == 0 +} + +var big8 = big.NewInt(8) + +func (s EIP155Signer) Sender(tx *Transaction) (types.Address, error) { + if tx.Type() != LegacyTxType { + return types.Address{}, ErrTxTypeNotSupported + } + if !tx.Protected() { + return HomesteadSigner{}.Sender(tx) + } + chainId, _ := uint256.FromBig(s.chainId) + id := tx.ChainId() + if id.Cmp(chainId) != 0 { + return types.Address{}, ErrInvalidChainId + } + V, R, S := tx.RawSignatureValues() + V1 := V.ToBig() + V1 = new(big.Int).Sub(V1, s.chainIdMul) + V1.Sub(V1, big8) + return recoverPlain(s.Hash(tx), R.ToBig(), S.ToBig(), V1, true) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported + } + R, S, V = decodeSignature(sig) + if s.chainId.Sign() != 0 { + V = big.NewInt(int64(sig[64] + 35)) + V.Add(V, s.chainIdMul) + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s EIP155Signer) Hash(tx *Transaction) types.Hash { + return hash.RlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + s.chainId, uint(0), uint(0), + }) +} + +// HomesteadTransaction implements TransactionInterface using the +// homestead rules. +type HomesteadSigner struct{ FrontierSigner } + +func (s HomesteadSigner) ChainID() *big.Int { + return nil +} + +func (s HomesteadSigner) Equal(s2 Signer) bool { + _, ok := s2.(HomesteadSigner) + return ok +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + return hs.FrontierSigner.SignatureValues(tx, sig) +} + +func (hs HomesteadSigner) Sender(tx *Transaction) (types.Address, error) { + if tx.Type() != LegacyTxType { + return types.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(hs.Hash(tx), r.ToBig(), s.ToBig(), v.ToBig(), true) +} + +type FrontierSigner struct{} + +func (s FrontierSigner) ChainID() *big.Int { + return nil +} + +func (s FrontierSigner) Equal(s2 Signer) bool { + _, ok := s2.(FrontierSigner) + return ok +} + +func (fs FrontierSigner) Sender(tx *Transaction) (types.Address, error) { + if tx.Type() != LegacyTxType { + return types.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(fs.Hash(tx), r.ToBig(), s.ToBig(), v.ToBig(), false) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported + } + r, s, v = decodeSignature(sig) + return r, s, v, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (fs FrontierSigner) Hash(tx *Transaction) types.Hash { + return hash.RlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + }) +} + +func decodeSignature(sig []byte) (r, s, v *big.Int) { + if len(sig) != crypto.SignatureLength { + panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + } + r = new(big.Int).SetBytes(sig[:32]) + s = new(big.Int).SetBytes(sig[32:64]) + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + return r, s, v +} + +func recoverPlain(sighash types.Hash, R, S, Vb *big.Int, homestead bool) (types.Address, error) { + if Vb.BitLen() > 8 { + return types.Address{}, ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + lr, _ := uint256.FromBig(R) + ls, _ := uint256.FromBig(S) + if !crypto.ValidateSignatureValues(V, lr, ls, homestead) { + return types.Address{}, ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + // recover the public key from the signature + pub, err := crypto.Ecrecover(sighash[:], sig) + if err != nil { + return types.Address{}, err + } + if len(pub) == 0 || pub[0] != 4 { + return types.Address{}, errors.New("invalid public key") + } + var addr types.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +// deriveChainID derives the chain id from the given v parameter +func DeriveChainId(v *uint256.Int) *uint256.Int { + if v.IsUint64() { + v := v.Uint64() + if v == 27 || v == 28 { + return new(uint256.Int) + } + return new(uint256.Int).SetUint64((v - 35) / 2) + } + r := new(uint256.Int).Sub(v, u256.Num35) + return r.Div(r, u256.Num2) +} diff --git a/common/transaction/transaction_test.go b/common/transaction/transaction_test.go new file mode 100644 index 0000000..f494484 --- /dev/null +++ b/common/transaction/transaction_test.go @@ -0,0 +1,88 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package transaction + +import ( + "crypto/rand" + "encoding/json" + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + "github.com/libp2p/go-libp2p/core/crypto" + "testing" +) + +func TestNewLegacyTx(t *testing.T) { + _, pub, err := crypto.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := types.PublicToAddress(pub) + + tx := NewTransaction(1, addr, &addr, uint256.NewInt(10000), 21000, uint256.NewInt(10000000), []byte("hello")) + t.Logf("tx: %v", tx) + + buf1, err := json.Marshal(tx) + t.Log(types.BytesHash(buf1).String()) + + switch txt := tx.inner.(type) { + case *LegacyTx: + buf, err := json.Marshal(txt) + if err != nil { + t.Fatal(err) + } + + txHash := types.BytesHash(buf) + t.Log(txHash.String()) + } + + hash := tx.Hash() + + t.Log(hash.String()) + +} + +func TestTDin(t *testing.T) { + _, pub, err := crypto.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := types.PublicToAddress(pub) + + tx := NewTransaction(1, addr, &addr, uint256.NewInt(10000), 21000, uint256.NewInt(10000000), []byte("hello")) + t.Logf("tx: %v", tx) + + b, err := tx.Marshal() + if err != nil { + t.Fatal(err) + } + + tx.Unmarshal(b) + + t.Log(b) +} + +func TestNewDynamicTx(t *testing.T) { + //_, pub, err := crypto.GenerateECDSAKeyPair(rand.Reader) + //if err != nil { + // t.Fatal(err) + //} + // + //addr := types.PublicToAddress(pub) + +} diff --git a/common/types/address.go b/common/types/address.go new file mode 100644 index 0000000..249b97b --- /dev/null +++ b/common/types/address.go @@ -0,0 +1,272 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "bytes" + "database/sql/driver" + "encoding/hex" + "fmt" + "strings" + + "github.com/astranetworld/ast/common/hexutil" + "github.com/libp2p/go-libp2p/core/crypto" + "golang.org/x/crypto/sha3" +) + +const ( + AddressLength = 20 + // IncarnationLength length of uint64 for contract incarnations + IncarnationLength = 2 + // Address32Length is the expected length of the Starknet address (in bytes) + Address32Length = 32 +) + +var ( + prefixAddress = "ast" + nullAddress = Address{0} +) + +type Address [AddressLength]byte + +// BytesToAddress returns Address with value b. +// If b is larger than len(h), b will be cropped from the left. +func BytesToAddress(b []byte) Address { + var a Address + a.SetBytes(b) + return a +} + +// HexToAddress returns Address with byte values of s. +// If s is larger than len(h), s will be cropped from the left. +func HexToAddress(s string) Address { return BytesToAddress(FromHex1(s)) } + +func PublicToAddress(key crypto.PubKey) Address { + bPub, err := crypto.MarshalPublicKey(key) + if err != nil { + return Address{0} + } + + h := sha3.New256() + h.Write(bPub) + hash := h.Sum(nil) + var addr Address + copy(addr[:], hash[:AddressLength]) + return addr +} + +func PrivateToAddress(key crypto.PrivKey) Address { + return PublicToAddress(key.GetPublic()) +} + +func HexToString(hexs string) (Address, error) { + a := Address{0} + if !strings.HasPrefix(strings.ToUpper(hexs), prefixAddress) { + return a, fmt.Errorf("invalid prefix address") + } + + b, err := hex.DecodeString(hexs[len(prefixAddress):]) + if err != nil { + return a, err + } + + copy(a[:], b) + + return a, nil +} + +// IsHexAddress verifies whether a string can represent a valid hex-encoded +// Ethereum address or not. +func IsHexAddress(s string) bool { + if has0xPrefix(s) { + s = s[2:] + } + return len(s) == 2*AddressLength && isHex(s) +} + +// Bytes gets the string representation of the underlying address. +func (a Address) Bytes() []byte { return a[:] } + +// Hash converts an address to a hash by left-padding it with zeros. +func (a Address) Hash() Hash { return BytesToHash(a[:]) } + +// Hex returns an EIP55-compliant hex string representation of the address. +func (a Address) Hex() string { + return string(a.checksumHex()) +} + +// String implements fmt.Stringer. +func (a Address) String() string { + return a.Hex() +} + +// Addresses is a slice of common.Address, implementing sort.Interface +type Addresses []Address + +func (addrs Addresses) Len() int { + return len(addrs) +} +func (addrs Addresses) Less(i, j int) bool { + return bytes.Compare(addrs[i][:], addrs[j][:]) == -1 +} +func (addrs Addresses) Swap(i, j int) { + addrs[i], addrs[j] = addrs[j], addrs[i] +} + +func (a *Address) checksumHex() []byte { + buf := a.hex() + + // compute checksum + sha := sha3.NewLegacyKeccak256() + //nolint:errcheck + sha.Write(buf[2:]) + hash := sha.Sum(nil) + for i := 2; i < len(buf); i++ { + hashByte := hash[(i-2)/2] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if buf[i] > '9' && hashByte > 7 { + buf[i] -= 32 + } + } + return buf +} + +func (a Address) hex() []byte { + var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") + hex.Encode(buf[2:], a[:]) + return buf[:] +} + +func (a *Address) DecodeBytes(b []byte) bool { + if len(b) != AddressLength { + return false + } + + copy(a[:], b) + return true +} + +func (a *Address) DecodeString(s string) bool { + if !strings.HasPrefix(strings.ToUpper(s), prefixAddress) { + return false + } + + b, err := hex.DecodeString(s[len(prefixAddress):]) + if err != nil { + a = &Address{0} + return false + } + + copy(a[:], b) + return true +} + +func (a Address) Equal(other Address) bool { + return bytes.Equal(a[:], other[:]) +} + +func (a *Address) IsNull() bool { + return bytes.Equal(a[:], nullAddress[:]) +} + +func (a Address) Marshal() ([]byte, error) { + return a.Bytes(), nil +} + +func (a *Address) MarshalTo(data []byte) (n int, err error) { + copy(data, a[:]) + return len(data), nil +} + +func (a *Address) Unmarshal(data []byte) error { + if len(data) != AddressLength { + return fmt.Errorf("invalid bytes len: %d, hex: %s", len(data), a.Hex()) + } + + copy(a[:], data) + return nil +} + +// SetBytes sets the address to the value of b. +// If b is larger than len(a), b will be cropped from the left. +func (a *Address) SetBytes(b []byte) *Address { + if len(b) > len(a) { + b = b[len(b)-AddressLength:] + } + copy(a[AddressLength-len(b):], b) + return a +} + +// MarshalText returns the hex representation of a. +func (a Address) MarshalText() ([]byte, error) { + return hexutil.Bytes(a[:]).MarshalText() +} + +// UnmarshalText parses a hash in hex syntax. +func (a *Address) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Address", input, a[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (a *Address) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) +} + +// Scan implements Scanner for database/sql. +func (a *Address) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Address", src) + } + if len(srcB) != AddressLength { + return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength) + } + copy(a[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (a Address) Value() (driver.Value, error) { + return a[:], nil +} + +func (a Address) Size() int { + return AddressLength +} + +// isHex validates whether each byte is valid hexadecimal string. +func isHex(str string) bool { + if len(str)%2 != 0 { + return false + } + for _, c := range []byte(str) { + if !isHexCharacter(c) { + return false + } + } + return true +} + +// isHexCharacter returns bool of c being a valid hexadecimal. +func isHexCharacter(c byte) bool { + return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') +} diff --git a/common/types/address_test.go b/common/types/address_test.go new file mode 100644 index 0000000..eba667d --- /dev/null +++ b/common/types/address_test.go @@ -0,0 +1,194 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "crypto/rand" + "encoding/json" + "github.com/holiman/uint256" + "reflect" + + // "github.com/astranetworld/ast/utils" cycle import + "strings" + "testing" + + "github.com/libp2p/go-libp2p/core/crypto" +) + +func TestAddress_ed25519(t *testing.T) { + _, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := PublicToAddress(pub) + t.Log(addr.String()) +} + +func TestFromECDSAPub(t *testing.T) { + _, pub, err := crypto.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := PublicToAddress(pub) + t.Log(addr.String()) +} + +// func TestAddress_DecodeString(t *testing.T) { +// _, pub, err := crypto.GenerateEd25519Key(rand.Reader) +// if err != nil { +// t.Fatal(err) +// } + +// addr := PublicToAddress(pub) +// t.Log(addr.String()) +// t.Log(addr.Bytes()) + +// h := addr.HexBytes() +// var c Address +// if c.DecodeHexBytes(h) { +// t.Log(h) +// t.Logf("c: %s", c.String()) +// } + +// var a, b Address +// if a.DecodeString(addr.String()) { +// t.Logf("a: %s", a.String()) +// } +// if b.DecodeBytes(addr.Bytes()) { +// t.Logf("b: %s", b.String()) +// } + +// t.Log("done!") +// } + +func TestPrivateToAddress(t *testing.T) { + priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := PublicToAddress(pub) + t.Log(addr.String()) + addr2 := PrivateToAddress(priv) + t.Log(addr2.String()) +} + +func TestAddress_null(t *testing.T) { + a := Address{} + if a.IsNull() { + t.Log("null address") + } else { + t.Log(a) + } + + _, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := PublicToAddress(pub) + + if addr.IsNull() { + t.Log("null address") + } else { + t.Log(strings.ToUpper(addr.String())) + } +} + +func TestAddress(t *testing.T) { + for i := 0; i < 100; i++ { + _, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + + addr := PublicToAddress(pub) + + if addr.IsNull() { + t.Log("null address") + } else { + t.Log(strings.ToUpper(addr.String())) + } + } +} + +func TestSign(t *testing.T) { + data := []byte("hello") + //priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) + priv, pub, err := crypto.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + t.Fatal(err) + } + + s, err := priv.Sign(data) + if err != nil { + t.Fatal(err) + } + + t.Logf("sign len (%d)", len(s)) + + ok, err := pub.Verify(data, s) + if err != nil { + t.Fatal(err) + } + + t.Logf("sign len %d", len(s)) + if !ok { + t.Log("verify sign failed") + } else { + t.Log("verify success") + } +} + +// func TestAddressToInt(t *testing.T) { +// priv, _, err := crypto.GenerateECDSAKeyPair(rand.Reader) +// if err != nil { +// t.Fatal(err) +// } + +// sp, err := utils.PrivateToString(priv) + +// addr := PrivateToAddress(priv) +// t.Logf("address: %s", addr) +// t.Logf("private: %s", sp) +// } + +func TestAddressMarshal(t *testing.T) { + aa := make(map[Address]*uint256.Int) + var a1, a2 Address + a1.DecodeString("ast4541Fc1CCB4e042a3BaDFE46904F9D22d127B682") + a2.DecodeString("ast9BA336835422BAeFc537d75642959d2a866500a3") + aa[a1] = uint256.NewInt(1) + aa[a2] = uint256.NewInt(2) + + b, err := json.Marshal(aa) + if nil != err { + t.Fatal(err) + } + + bb := make(map[Address]*uint256.Int) + + if err := json.Unmarshal(b, &bb); nil != err { + t.Fatal(err) + } + + if !reflect.DeepEqual(aa, bb) { + t.Error("failed") + } +} diff --git a/common/types/bloom.go b/common/types/bloom.go new file mode 100644 index 0000000..12c6803 --- /dev/null +++ b/common/types/bloom.go @@ -0,0 +1,71 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "encoding/binary" + "fmt" + bloomfilter "github.com/holiman/bloomfilter/v2" +) + +// Collide rate +const probCollide = 0.0000001 + +type hasher []byte + +func (f hasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (f hasher) Sum(b []byte) []byte { panic("not implemented") } +func (f hasher) Reset() { panic("not implemented") } +func (f hasher) BlockSize() int { panic("not implemented") } +func (f hasher) Size() int { return 8 } +func (f hasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } + +type Bloom struct { + bloom *bloomfilter.Filter +} + +func NewBloom(size uint64) (*Bloom, error) { + bloom, err := bloomfilter.NewOptimal(size, probCollide) + if err != nil { + return nil, err + } + return &Bloom{bloom: bloom}, nil +} + +func (b *Bloom) UnMarshalBloom(data []byte) error { + err := b.bloom.UnmarshalBinary(data) + return err +} + +func (b *Bloom) Add(key []byte) error { + if len(key) != HashLength { + return fmt.Errorf("key length is not 32 ") + } + b.bloom.Add(hasher(key)) + return nil +} + +// Contain +// - true maybe in the set +// - false must not in the set +func (b *Bloom) Contain(key []byte) bool { + return b.bloom.Contains(hasher(key)) +} + +func (b *Bloom) Marshal() ([]byte, error) { + return b.bloom.MarshalBinary() +} diff --git a/common/types/bloom_test.go b/common/types/bloom_test.go new file mode 100644 index 0000000..641aaa2 --- /dev/null +++ b/common/types/bloom_test.go @@ -0,0 +1,41 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import "testing" + +func TestBloom_Contain(t *testing.T) { + bloom, _ := NewBloom(10) + hashes := []Hash{{0x01}, {0x02}, {0x03}, {0x04}, {0x05}, {0x06}, {0x07}, {0x08}, {0x09}, {0x0a}} + for _, hash := range hashes { + bloom.Add(hash.Bytes()) + } + + searchHash := []Hash{{0x10}, {0x11}, {0x01}} + + for _, hash := range searchHash { + if bloom.Contain(hash.Bytes()) { + t.Logf("hash %d is in hashes %d", hash, hashes) + } else { + t.Logf("hash %d is not in hashes %d", hash, hashes) + } + } + + b, _ := bloom.Marshal() + t.Logf("bloom Marshal: %+v", b) + +} diff --git a/common/types/bytes.go b/common/types/bytes.go new file mode 100644 index 0000000..1b0d3bb --- /dev/null +++ b/common/types/bytes.go @@ -0,0 +1,118 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "bytes" + "encoding/hex" +) + +// FromHex returns the bytes represented by the hexadecimal string s. +// s may be prefixed with "0x". +func FromHex1(s string) []byte { + if has0xPrefix(s) { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return Hex2Bytes(s) +} + +// has0xPrefix validates str begins with '0x' or '0X'. +func has0xPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} + +// Hex2Bytes returns the bytes represented by the hexadecimal string str. +func Hex2Bytes(str string) []byte { + h, _ := hex.DecodeString(str) + return h +} + +func KeyCmp(key1, key2 []byte) (int, bool) { + switch { + //both keys are empty + case len(key1) == 0 && len(key2) == 0: + return 0, true + // key1 is empty + case len(key1) == 0 && len(key2) != 0: + return 1, false + // key2 is empty + case len(key1) != 0 && len(key2) == 0: + return -1, false + default: + return bytes.Compare(key1, key2), false + } +} + +// CopyBytes returns an exact copy of the provided bytes. +func CopyBytes(b []byte) (copiedBytes []byte) { + if b == nil { + return nil + } + copiedBytes = make([]byte, len(b)) + copy(copiedBytes, b) + + return +} + +// RightPadBytes zero-pads slice to the right up to length l. +func RightPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded, slice) + + return padded +} + +// LeftPadBytes zero-pads slice to the left up to length l. +func LeftPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded[l-len(slice):], slice) + + return padded +} + +// TrimLeftZeroes returns a subslice of s without leading zeroes +func TrimLeftZeroes(s []byte) []byte { + idx := 0 + for ; idx < len(s); idx++ { + if s[idx] != 0 { + break + } + } + return s[idx:] +} + +// TrimRightZeroes returns a subslice of s without trailing zeroes +func TrimRightZeroes(s []byte) []byte { + idx := len(s) + for ; idx > 0; idx-- { + if s[idx-1] != 0 { + break + } + } + return s[:idx] +} diff --git a/common/types/hash.go b/common/types/hash.go new file mode 100644 index 0000000..2608ba8 --- /dev/null +++ b/common/types/hash.go @@ -0,0 +1,313 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "bytes" + "database/sql/driver" + "encoding/hex" + "fmt" + + "github.com/astranetworld/ast/common/hexutil" + + "hash" + "math/big" + "math/rand" + "reflect" + + "golang.org/x/crypto/sha3" +) + +const ( + HashLength = 32 +) + +var ( + hashT = reflect.TypeOf(Hash{}) + addressT = reflect.TypeOf(Address{}) + //addressSt = reflect.TypeOf(Address32{}) +) + +type Hash [HashLength]byte + +// Bytes gets the byte representation of the underlying hash. +func (h Hash) Bytes() []byte { return h[:] } + +// Big converts a hash to a big integer. +func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } + +// Hex converts a hash to a hex string. +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (h Hash) TerminalString() string { + return fmt.Sprintf("%x…%x", h[:3], h[29:]) +} + +// UnmarshalText parses a hash in hex syntax. +func (h *Hash) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Hash", input, h[:]) +} + +// MarshalText returns the hex representation of h. +func (h Hash) MarshalText() ([]byte, error) { + return hexutil.Bytes(h[:]).MarshalText() +} + +// SetBytes sets the hash to the value of b. +func (h *Hash) SetBytes(b []byte) error { + if len(b) != HashLength { + return fmt.Errorf("invalid bytes len %d", len(b)) + } + + copy(h[:], b[:HashLength]) + return nil +} + +// Generate implements testing/quick.Generator. +func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { + m := rand.Intn(len(h)) + for i := len(h) - 1; i > m; i-- { + h[i] = byte(rand.Uint32()) + } + return reflect.ValueOf(h) +} + +// Scan implements Scanner for database/sql. +func (h *Hash) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Hash", src) + } + if len(srcB) != HashLength { + return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength) + } + copy(h[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (h Hash) Value() (driver.Value, error) { + return h[:], nil +} + +func BytesHash(b []byte) Hash { + h3 := sha3.New256() + h3.Write(b) + r := h3.Sum(nil) + var h Hash + copy(h[:], r[:HashLength]) + return h +} + +func BytesToHash(b []byte) Hash { + var h Hash + h.SetBytes(b) + return h +} + +func StringToHash(s string) Hash { + var h Hash + b, err := hex.DecodeString(s) + if err == nil { + //copy(h[:], b[:HashLength]) + return BytesToHash(b) + } + + return h +} + +func (h Hash) String() string { + return hex.EncodeToString(h[:]) +} + +func (h Hash) HexBytes() []byte { + s := h.String() + return []byte(s) +} + +func (h *Hash) SetString(s string) error { + if len(s) != HashLength*2 { + return fmt.Errorf("invalid string len %v, len(%d)", string(s), len(s)) + } + + b, err := hex.DecodeString(s) + if err != nil { + return err + } + + return h.SetBytes(b) +} + +func (h Hash) Marshal() ([]byte, error) { + return h.Bytes(), nil +} + +func (h *Hash) MarshalTo(data []byte) (n int, err error) { + copy(data, h.Bytes()) + return len(h.Bytes()), err +} + +func (h *Hash) Unmarshal(data []byte) error { + return h.SetBytes(data) +} + +func (h *Hash) Size() int { + return len(h.Bytes()) +} + +// Format implements fmt.Formatter. +// Hash supports the %v, %s, %v, %x, %X and %d format verbs. +func (h Hash) Format(s fmt.State, c rune) { + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) + } +} + +// UnmarshalJSON parses a hash in hex syntax. +func (h *Hash) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) +} + +func (h Hash) Equal(other Hash) bool { + return bytes.Equal(h.Bytes(), other.Bytes()) +} + +// HashDifference returns a new set which is the difference between a and b. +func HashDifference(a, b []Hash) []Hash { + keep := make([]Hash, 0, len(a)) + + remove := make(map[Hash]struct{}) + for _, hash := range b { + remove[hash] = struct{}{} + } + + for _, hash := range a { + if _, ok := remove[hash]; !ok { + keep = append(keep, hash) + } + } + + return keep +} + +// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type keccakState interface { + hash.Hash + Read([]byte) (int, error) +} + +type Hasher struct { + Sha keccakState +} + +var hasherPools = make(chan *Hasher, 128) + +func NewHasher() *Hasher { + var h *Hasher + select { + case h = <-hasherPools: + default: + h = &Hasher{Sha: sha3.NewLegacyKeccak256().(keccakState)} + } + return h +} + +func ReturnHasherToPool(h *Hasher) { + select { + case hasherPools <- h: + default: + fmt.Printf("Allowing Hasher to be garbage collected, pool is full\n") + } +} + +func HashData(data []byte) (Hash, error) { + h := NewHasher() + defer ReturnHasherToPool(h) + h.Sha.Reset() + + _, err := h.Sha.Write(data) + if err != nil { + return Hash{}, err + } + + var buf Hash + _, err = h.Sha.Read(buf[:]) + if err != nil { + return Hash{}, err + } + return buf, nil +} + +// Bytes2Hex returns the hexadecimal encoding of d. +func Bytes2Hex(d []byte) string { + return hex.EncodeToString(d) +} + +// HexToHash sets byte representation of s to hash. +// If b is larger than len(h), b will be cropped from the left. +func HexToHash(s string) Hash { return BytesToHash(FromHex2Bytes(s)) } + +// FromHex returns the bytes represented by the hexadecimal string s. +// s may be prefixed with "0x". +func FromHex2Bytes(s string) []byte { + if has0xPrefix(s) { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return Hex2Bytes(s) +} + +// Hashes is a slice of common.Hash, implementing sort.Interface +type Hashes []Hash + +func (hashes Hashes) Len() int { + return len(hashes) +} +func (hashes Hashes) Less(i, j int) bool { + return bytes.Compare(hashes[i][:], hashes[j][:]) == -1 +} +func (hashes Hashes) Swap(i, j int) { + hashes[i], hashes[j] = hashes[j], hashes[i] +} diff --git a/common/types/int256.go b/common/types/int256.go new file mode 100644 index 0000000..d106794 --- /dev/null +++ b/common/types/int256.go @@ -0,0 +1,33 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "github.com/holiman/uint256" + "math/big" +) + +func Int256Min(a, b *uint256.Int) *uint256.Int { + if a.Cmp(b) > 0 { + return b + } + return a +} + +// BigToHash sets byte representation of b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } diff --git a/common/types/signature.go b/common/types/signature.go new file mode 100644 index 0000000..19cba22 --- /dev/null +++ b/common/types/signature.go @@ -0,0 +1,183 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/astranetworld/ast/common/hexutil" + "reflect" +) + +const SignatureLength = 96 +const PublicKeyLength = 48 + +var ( + signatureT = reflect.TypeOf(Signature{}) + publicKeyT = reflect.TypeOf(PublicKey{}) +) + +type Signature [SignatureLength]byte + +func (h Signature) Hex() string { return hexutil.Encode(h[:]) } +func (h Signature) String() string { + return h.Hex() +} + +func (h Signature) Size() int { + return SignatureLength +} + +func (h Signature) Bytes() []byte { return h[:] } + +func (h *Signature) SetBytes(data []byte) error { + if len(data) != SignatureLength { + return fmt.Errorf("invalid bytes len %d", len(data)) + } + copy(h[:], data[:SignatureLength]) + return nil +} + +func (h Signature) Marshal() ([]byte, error) { + return h.Bytes(), nil +} + +func (h *Signature) Unmarshal(data []byte) error { + return h.SetBytes(data) +} + +// Hash supports the %v, %s, %q, %x, %X and %d format verbs. +func (h Signature) Format(s fmt.State, c rune) { + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(signature=%x)", c, h) + } +} + +// UnmarshalText parses a hash in hex syntax. +func (h *Signature) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Signature", input, h[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (h *Signature) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(signatureT, input, h[:]) +} + +// MarshalText returns the hex representation of h. +func (h Signature) MarshalText() ([]byte, error) { + return hexutil.Bytes(h[:]).MarshalText() +} + +type PublicKey [PublicKeyLength]byte + +func (h PublicKey) Hex() string { return hexutil.Encode(h[:]) } +func (h PublicKey) String() string { + return h.Hex() +} + +func (h PublicKey) Bytes() []byte { + return h[:] +} + +func (h *PublicKey) SetBytes(b []byte) error { + if len(b) != PublicKeyLength { + return fmt.Errorf("invalid bytes len %d", len(b)) + } + + copy(h[:], b[:PublicKeyLength]) + return nil +} + +func (h PublicKey) Size() int { + return PublicKeyLength +} + +func (h PublicKey) Marshal() ([]byte, error) { + return h.Bytes(), nil +} + +func (h *PublicKey) Unmarshal(data []byte) error { + return h.SetBytes(data) +} + +// MarshalText returns the hex representation of a. +func (a PublicKey) MarshalText() ([]byte, error) { + return hexutil.Bytes(a[:]).MarshalText() +} + +// UnmarshalText parses a hash in hex syntax. +func (a *PublicKey) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("PublicKey", input, a[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (a *PublicKey) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(publicKeyT, input, a[:]) +} + +// Format implements fmt.Formatter. +// supports the %v, %s, %q, %x, %X and %d format verbs. +func (h PublicKey) Format(s fmt.State, c rune) { + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(publickey=%x)", c, h) + } +} diff --git a/common/types/size.go b/common/types/size.go new file mode 100644 index 0000000..4fa0cb0 --- /dev/null +++ b/common/types/size.go @@ -0,0 +1,56 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package types + +import ( + "fmt" +) + +// StorageSize is a wrapper around a float value that supports user friendly +// formatting. +type StorageSize float64 + +// String implements the stringer interface. +func (s StorageSize) String() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2f TiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2f GiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2f MiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2f KiB", s/1024) + } else { + return fmt.Sprintf("%.2f B", s) + } +} + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (s StorageSize) TerminalString() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2fTiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2fGiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2fMiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2fKiB", s/1024) + } else { + return fmt.Sprintf("%.2fB", s) + } +} diff --git a/common/types/ssz/sszuint64.go b/common/types/ssz/sszuint64.go new file mode 100644 index 0000000..e417349 --- /dev/null +++ b/common/types/ssz/sszuint64.go @@ -0,0 +1,57 @@ +package ssztype + +import ( + "encoding/binary" + "fmt" + + fssz "github.com/prysmaticlabs/fastssz" +) + +// SSZUint64 -- +type SSZUint64 uint64 + +// SizeSSZ -- +func (s *SSZUint64) SizeSSZ() int { + return 8 +} + +// MarshalSSZTo -- +func (s *SSZUint64) MarshalSSZTo(dst []byte) ([]byte, error) { + marshalled, err := s.MarshalSSZ() + if err != nil { + return nil, err + } + return append(dst, marshalled...), nil +} + +// MarshalSSZ -- +func (s *SSZUint64) MarshalSSZ() ([]byte, error) { + marshalled := fssz.MarshalUint64([]byte{}, uint64(*s)) + return marshalled, nil +} + +// UnmarshalSSZ -- +func (s *SSZUint64) UnmarshalSSZ(buf []byte) error { + if len(buf) != s.SizeSSZ() { + return fmt.Errorf("expected buffer of length %d received %d", s.SizeSSZ(), len(buf)) + } + *s = SSZUint64(fssz.UnmarshallUint64(buf)) + return nil +} + +// HashTreeRoot -- +func (s *SSZUint64) HashTreeRoot() ([32]byte, error) { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(*s)) + var root [32]byte + copy(root[:], buf) + return root, nil +} + +// HashTreeRootWith -- +func (s *SSZUint64) HashTreeRootWith(hh *fssz.Hasher) error { + indx := hh.Index() + hh.PutUint64(uint64(*s)) + hh.Merkleize(indx) + return nil +} diff --git a/common/u256/big.go b/common/u256/big.go new file mode 100644 index 0000000..0abc9b7 --- /dev/null +++ b/common/u256/big.go @@ -0,0 +1,32 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . +package u256 + +import ( + "github.com/holiman/uint256" +) + +// Common big integers often used +var ( + Num0 = uint256.NewInt(0) + Num1 = uint256.NewInt(1) + Num2 = uint256.NewInt(2) + Num4 = uint256.NewInt(4) + Num8 = uint256.NewInt(8) + Num27 = uint256.NewInt(27) + Num32 = uint256.NewInt(32) + Num35 = uint256.NewInt(35) +) diff --git a/conf/account_config.go b/conf/account_config.go new file mode 100644 index 0000000..62b771c --- /dev/null +++ b/conf/account_config.go @@ -0,0 +1,21 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type AccountConfig struct { + PasswordFile string `json:"password_file" yaml:"password_file"` +} diff --git a/conf/config.go b/conf/config.go new file mode 100644 index 0000000..5066f91 --- /dev/null +++ b/conf/config.go @@ -0,0 +1,71 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import ( + "bufio" + "fmt" + "github.com/astranetworld/ast/params" + "os" + + "gopkg.in/yaml.v2" +) + +type Config struct { + NodeCfg NodeConfig `json:"node" yaml:"node"` + NetworkCfg NetWorkConfig `json:"network" yaml:"network"` + LoggerCfg LoggerConfig `json:"logger" yaml:"logger"` + DatabaseCfg DatabaseConfig `json:"database" yaml:"database"` + PprofCfg PprofConfig `json:"pprof" yaml:"pprof"` + ChainCfg *params.ChainConfig `json:"chain" yaml:"chain"` + AccountCfg AccountConfig `json:"account" yaml:"account"` + MetricsCfg MetricsConfig `json:"metrics" yaml:"metrics"` + P2PCfg *P2PConfig `json:"p2p" yaml:"p2p"` + // Gas Price Oracle options + GPO GpoConfig `json:"gpo" yaml:"gpo"` + Miner MinerConfig `json:"miner"` +} + +func SaveConfigToFile(file string, config Config) error { + if len(file) == 0 { + file = "./config2.yaml" + } + + fd, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) + if err != nil { + //log.Errorf("filed open file %v, err %v", file, err) + return err + } + defer fd.Close() + return yaml.NewEncoder(fd).Encode(config) + //return toml.NewEncoder(fd).Encode(blockchain) +} + +func LoadConfigFromFile(file string, config *Config) error { + if len(file) <= 0 { + return fmt.Errorf("failed to load blockchain from file, file is nil") + } + //_, err := toml.DecodeFile(file, blockchain) + fd, err := os.Open(file) + if err != nil { + return err + } + defer fd.Close() + reader := bufio.NewReader(fd) + //return toml.NewDecoder(reader).Decode(blockchain) + return yaml.NewDecoder(reader).Decode(config) +} diff --git a/conf/consensus_config.go b/conf/consensus_config.go new file mode 100644 index 0000000..0adf595 --- /dev/null +++ b/conf/consensus_config.go @@ -0,0 +1,50 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import "math/big" + +type ConsensusConfig struct { + EngineName string `json:"name" yaml:"name"` + Etherbase string `json:"etherbase" yaml:"etherbase"` + Period uint64 `json:"period" yaml:"period"` + APoa *APoaConfig `json:"apoa" yaml:"poa"` + GasFloor uint64 `json:"gasFloor" yaml:"gasFloor"` // Target gas floor for mined blocks. + GasCeil uint64 `json:"gasCeil" yaml:"gasCeil"` // Target gas ceiling for mined blocks. + APos *APosConfig `json:"apos" yaml:"pos"` +} + +type APoaConfig struct { + Epoch uint64 `json:"epoch" yaml:"epoch"` + CheckpointInterval uint64 `json:"checkpointInterval" yaml:"checkpointInterval"` + InmemorySnapshots int `json:"inmemorySnapshots" yaml:"inmemorySnapshots"` + InmemorySignatures int `json:"inmemorySignatures" yaml:"inmemorySignatures"` + InMemory bool `json:"inMemory" yaml:"inMemory"` +} + +type APosConfig struct { + Epoch uint64 `json:"epoch" yaml:"epoch"` + CheckpointInterval uint64 `json:"checkpointInterval" yaml:"checkpointInterval"` + InmemorySnapshots int `json:"inmemorySnapshots" yaml:"inmemorySnapshots"` + InmemorySignatures int `json:"inmemorySignatures" yaml:"inmemorySignatures"` + InMemory bool `json:"inMemory" yaml:"inMemory"` + RewardEpoch uint64 `json:"rewardEpoch" yaml:"rewardEpoch"` + RewardLimit *big.Int `json:"rewardLimit" yaml:"rewardLimit"` + + DepositContract string `json:"depositContract" yaml:"depositContract"` // Deposit contract + DepositNFTContract string `json:"depositNFTContract" yaml:"depositNFTContract"` // Deposit NFT contract +} diff --git a/conf/databae_config.go b/conf/databae_config.go new file mode 100644 index 0000000..b53012d --- /dev/null +++ b/conf/databae_config.go @@ -0,0 +1,28 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type DatabaseConfig struct { + DBType string `json:"db_type" yaml:"db_type"` + DBPath string `json:"path" yaml:"path"` + DBName string `json:"name" yaml:"name"` + SubDB []string `json:"sub_name" yaml:"sub_name"` + Debug bool `json:"debug" yaml:"debug"` + IsMem bool `json:"memory" yaml:"memory"` + MaxDB uint64 `json:"max_db" yaml:"max_db"` + MaxReaders uint64 `json:"max_readers" yaml:"max_readers"` +} diff --git a/conf/gasprice.go b/conf/gasprice.go new file mode 100644 index 0000000..61b9455 --- /dev/null +++ b/conf/gasprice.go @@ -0,0 +1,57 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import ( + "github.com/astranetworld/ast/params" + "math/big" +) + +var ( + DefaultMaxPrice = big.NewInt(500 * params.GWei) + DefaultIgnorePrice = big.NewInt(2 * params.Wei) +) + +type GpoConfig struct { + Blocks int + Percentile int + MaxHeaderHistory int + MaxBlockHistory int + Default *big.Int `toml:",omitempty"` + MaxPrice *big.Int `toml:",omitempty"` + IgnorePrice *big.Int `toml:",omitempty"` +} + +// FullNodeGPO contains default gasprice oracle settings for full node. +var FullNodeGPO = GpoConfig{ + Blocks: 20, + Percentile: 60, + MaxHeaderHistory: 1024, + MaxBlockHistory: 1024, + MaxPrice: DefaultMaxPrice, + IgnorePrice: DefaultIgnorePrice, +} + +// LightClientGPO contains default gasprice oracle settings for light client. +var LightClientGPO = GpoConfig{ + Blocks: 2, + Percentile: 60, + MaxHeaderHistory: 300, + MaxBlockHistory: 5, + MaxPrice: DefaultMaxPrice, + IgnorePrice: DefaultIgnorePrice, +} diff --git a/conf/genesis_config.go b/conf/genesis_config.go new file mode 100644 index 0000000..9279acb --- /dev/null +++ b/conf/genesis_config.go @@ -0,0 +1,57 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import ( + "github.com/astranetworld/ast/common/types" + "github.com/holiman/uint256" + + "github.com/astranetworld/ast/params" +) + +type Genesis struct { + Config *params.ChainConfig `json:"config" yaml:"config"` + Nonce uint64 `json:"nonce"` + Timestamp uint64 `json:"timestamp"` + ExtraData []byte `json:"extraData"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + Difficulty *uint256.Int `json:"difficulty" gencodec:"required"` + Mixhash types.Hash `json:"mixHash"` + Coinbase types.Address `json:"coinbase"` + + //Engine *ConsensusConfig `json:"engine" yaml:"engine"` + Miners []string `json:"miners" yaml:"miners"` + Alloc GenesisAlloc `json:"alloc" yaml:"alloc" gencodec:"required"` + + // These fields are used for consensus tests. Please don't use them + // in actual genesis blocks. + Number uint64 `json:"number"` + GasUsed uint64 `json:"gasUsed"` + ParentHash types.Hash `json:"parentHash"` + BaseFee *uint256.Int `json:"baseFeePerGas"` +} + +// GenesisAlloc specifies the initial state that is part of the genesis block. +type GenesisAlloc map[types.Address]GenesisAccount + +type GenesisAccount struct { + //Address string `json:"address" toml:"address"` + Balance string `json:"balance"` + Code []byte `json:"code,omitempty"` + Storage map[types.Hash]types.Hash `json:"storage,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` +} diff --git a/conf/logger_config.go b/conf/logger_config.go new file mode 100644 index 0000000..ea310bb --- /dev/null +++ b/conf/logger_config.go @@ -0,0 +1,26 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type LoggerConfig struct { + LogFile string `json:"name" yaml:"name"` + Level string `json:"level" yaml:"level"` + MaxSize int `json:"max_size" yaml:"max_size"` + MaxBackups int `json:"max_count" yaml:"max_count"` + MaxAge int `json:"max_day" yaml:"max_day"` + Compress bool `json:"compress" yaml:"compress"` +} diff --git a/conf/metrics_config.go b/conf/metrics_config.go new file mode 100644 index 0000000..6bfb360 --- /dev/null +++ b/conf/metrics_config.go @@ -0,0 +1,23 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type MetricsConfig struct { + Enable bool `json:"enable" yaml:"enable"` + Port int `json:"port" yaml:"port"` + HTTP string `json:"http" yaml:"http"` +} diff --git a/conf/miner.go b/conf/miner.go new file mode 100644 index 0000000..fe5cc4c --- /dev/null +++ b/conf/miner.go @@ -0,0 +1,29 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import ( + "math/big" + "time" +) + +type MinerConfig struct { + Etherbase string // Public address for block mining rewards + GasCeil uint64 // Target gas ceiling for mined blocks. + GasPrice *big.Int // Minimum gas price for mining a transaction + Recommit time.Duration // The time interval for miner to re-create mining work +} diff --git a/conf/node_config.go b/conf/node_config.go new file mode 100644 index 0000000..9389f6b --- /dev/null +++ b/conf/node_config.go @@ -0,0 +1,138 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +import ( + "os" + "path/filepath" +) + +const ( + datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore +) + +type NodeConfig struct { + NodePrivate string `json:"private" yaml:"private"` + HTTP bool `json:"http" yaml:"http" ` + HTTPHost string `json:"http_host" yaml:"http_host" ` + HTTPPort string `json:"http_port" yaml:"http_port"` + HTTPApi string `json:"http_api" yaml:"http_api"` + // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting + // clients. Please be aware that CORS is a browser enforced security, it's fully + // useless for custom HTTP clients. + HTTPCors string `json:"http_cors" yaml:"http_cors"` + + WS bool `json:"ws" yaml:"ws" ` + WSHost string `json:"ws_host" yaml:"ws_host" ` + WSPort string `json:"ws_port" yaml:"ws_port"` + WSApi string `json:"ws_api" yaml:"ws_api"` + // WSOrigins is the list of domain to accept websocket requests from. Please be + // aware that the server can only act upon the HTTP request the client sends and + // cannot verify the validity of the request header. + WSOrigins string `toml:",omitempty"` + IPCPath string `json:"ipc_path" yaml:"ipc_path"` + DataDir string `json:"data_dir" yaml:"data_dir"` + MinFreeDiskSpace int `json:"min_free_disk_space" yaml:"min_free_disk_space"` + Chain string `json:"chain" yaml:"chain"` + Miner bool `json:"miner" yaml:"miner"` + + AuthRPC bool `json:"auth_rpc" yaml:"auth_rpc"` + // AuthAddr is the listening address on which authenticated APIs are provided. + AuthAddr string `json:"auth_addr" yaml:"auth_addr"` + + // AuthPort is the port number on which authenticated APIs are provided. + AuthPort int `json:"auth_port" yaml:"auth_port"` + + // AuthVirtualHosts is the list of virtual hostnames which are allowed on incoming requests + // for the authenticated api. This is by default {'localhost'}. + AuthVirtualHosts []string `json:"auth_virtual_hosts" yaml:"auth_virtual_hosts"` + + // JWTSecret is the path to the hex-encoded jwt secret. + JWTSecret string `json:"jwt_secret" yaml:"jwt_secret"` + + // KeyStoreDir is the file system folder that contains private keys. The directory can + // be specified as a relative path, in which case it is resolved relative to the + // current directory. + // + // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of + // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory + // is created by New and destroyed when the node is stopped. + KeyStoreDir string `json:"key_store_dir" yaml:"key_store_dir"` + + // ExternalSigner specifies an external URI for a clef-type signer + ExternalSigner string `json:"external_signer" yaml:"external_signer"` + + // UseLightweightKDF lowers the memory and CPU requirements of the key store + // scrypt KDF at the expense of security. + UseLightweightKDF bool `json:"use_lightweight_kdf" yaml:"use_lightweight_kdf"` + + // InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment. + InsecureUnlockAllowed bool `json:"insecure_unlock_allowed" yaml:"insecure_unlock_allowed"` + + PasswordFile string `json:"password_file" yaml:"password_file"` +} + +// KeyDirConfig determines the settings for keydirectory +func (c *NodeConfig) KeyDirConfig() (string, error) { + var ( + keydir string + err error + ) + switch { + case filepath.IsAbs(c.KeyStoreDir): + keydir = c.KeyStoreDir + case c.DataDir != "": + if c.KeyStoreDir == "" { + keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore) + } else { + keydir, err = filepath.Abs(c.KeyStoreDir) + } + case c.KeyStoreDir != "": + keydir, err = filepath.Abs(c.KeyStoreDir) + } + return keydir, err +} + +// getKeyStoreDir retrieves the key directory and will create +// and ephemeral one if necessary. +func getKeyStoreDir(conf *NodeConfig) (string, bool, error) { + keydir, err := conf.KeyDirConfig() + if err != nil { + return "", false, err + } + isEphemeral := false + if keydir == "" { + // There is no datadir. + keydir, err = os.MkdirTemp("", "astranet-keystore") + isEphemeral = true + } + + if err != nil { + return "", false, err + } + if err := os.MkdirAll(keydir, 0700); err != nil { + return "", false, err + } + + return keydir, isEphemeral, nil +} + +// ExtRPCEnabled returns the indicator whether node enables the external +// RPC(http, ws or graphql). +func (c *NodeConfig) ExtRPCEnabled() bool { + return c.HTTPHost != "" || c.WSHost != "" +} diff --git a/conf/p2p_config.go b/conf/p2p_config.go new file mode 100644 index 0000000..a7c1204 --- /dev/null +++ b/conf/p2p_config.go @@ -0,0 +1,54 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type NetWorkConfig struct { + ListenersAddress []string `json:"listeners" yaml:"listeners"` + BootstrapPeers []string `json:"bootstraps" yaml:"bootstraps"` + LocalPeerKey string `json:"private" yaml:"network_private"` + Bootstrapped bool `json:"discover" yaml:"discover"` +} + +type P2PConfig struct { + NoDiscovery bool `json:"no_discovery" yaml:"no_discovery"` + EnableUPnP bool `json:"enable_upnp" yaml:"enable_upnp"` + StaticPeerID bool `json:"static_peer_id" yaml:"static_peer_id"` + StaticPeers []string `json:"static_peers" yaml:"static_peers"` + BootstrapNodeAddr []string `json:"bootstrap_node_addr" yaml:"bootstrap_node_addr"` + Discv5BootStrapAddr []string `json:"discv5_bootstrap_addr" yaml:"discv5_bootstrap_addr"` + RelayNodeAddr string `json:"relay_node_addr" yaml:"relay_node_addr"` + LocalIP string `json:"local_ip" yaml:"local_ip"` + HostAddress string `json:"host_address" yaml:"host_address"` + HostDNS string `json:"host_dns" yaml:"host_dns"` + PrivateKey string `json:"private_key" yaml:"private_key"` + DataDir string `json:"data_dir" yaml:"data_dir"` + MetaDataDir string `json:"metadata_dir" yaml:"metadata_dir"` + TCPPort int `json:"tcp_port" yaml:"tcp_port"` + UDPPort int `json:"udp_port" yaml:"udp_port"` + MaxPeers int `json:"max_peers" yaml:"max_peers"` + AllowListCIDR string `json:"allow_list_cidr" yaml:"allow_list_cidr"` + DenyListCIDR []string `json:"deny_list_cidr" yaml:"deny_list_cidr"` + MinSyncPeers int `json:"min_sync_peers" yaml:"min_sync_peers"` + + P2PLimit *P2PLimit +} + +type P2PLimit struct { + BlockBatchLimit int `json:"block_batch_limit" yaml:"block_batch_limit"` + BlockBatchLimitBurstFactor int `json:"block_batch_limit_burst_factor" yaml:"block_batch_limit_burst_factor"` + BlockBatchLimiterPeriod int `json:"block_batch_limiter_period" yaml:"block_batch_limiter_period"` +} diff --git a/conf/pprof_config.go b/conf/pprof_config.go new file mode 100644 index 0000000..842b6ce --- /dev/null +++ b/conf/pprof_config.go @@ -0,0 +1,25 @@ +// Copyright 2022 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package conf + +type PprofConfig struct { + MaxCpu int `json:"cpu" yaml:"cpu"` + Port int `json:"port" yaml:"port"` + TraceMutex bool `json:"trace_mutex" yaml:"trace_mutex"` + TraceBlock bool `json:"trace_block" yaml:"trace_block"` + Pprof bool `json:"pprof" yaml:"pprof"` +} diff --git a/console/prompt/prompter.go b/console/prompt/prompter.go new file mode 100644 index 0000000..49640b4 --- /dev/null +++ b/console/prompt/prompter.go @@ -0,0 +1,172 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package prompt + +import ( + "fmt" + "strings" + + "github.com/peterh/liner" +) + +// Stdin holds the stdin line reader (also using stdout for printing prompts). +// Only this reader may be used for input because it keeps an internal buffer. +var Stdin = newTerminalPrompter() + +// UserPrompter defines the methods needed by the console to prompt the user for +// various types of inputs. +type UserPrompter interface { + // PromptInput displays the given prompt to the user and requests some textual + // data to be entered, returning the input of the user. + PromptInput(prompt string) (string, error) + + // PromptPassword displays the given prompt to the user and requests some textual + // data to be entered, but one which must not be echoed out into the terminal. + // The method returns the input provided by the user. + PromptPassword(prompt string) (string, error) + + // PromptConfirm displays the given prompt to the user and requests a boolean + // choice to be made, returning that choice. + PromptConfirm(prompt string) (bool, error) + + // SetHistory sets the input scrollback history that the prompter will allow + // the user to scroll back to. + SetHistory(history []string) + + // AppendHistory appends an entry to the scrollback history. It should be called + // if and only if the prompt to append was a valid command. + AppendHistory(command string) + + // ClearHistory clears the entire history + ClearHistory() + + // SetWordCompleter sets the completion function that the prompter will call to + // fetch completion candidates when the user presses tab. + SetWordCompleter(completer WordCompleter) +} + +// WordCompleter takes the currently edited line with the cursor position and +// returns the completion candidates for the partial word to be completed. If +// the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, +// wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world", +// "Word"}, "!!!") to have "Hello, world!!!". +type WordCompleter func(line string, pos int) (string, []string, string) + +// terminalPrompter is a UserPrompter backed by the liner package. It supports +// prompting the user for various input, among others for non-echoing password +// input. +type terminalPrompter struct { + *liner.State + warned bool + supported bool + normalMode liner.ModeApplier + rawMode liner.ModeApplier +} + +// newTerminalPrompter creates a liner based user input prompter working off the +// standard input and output streams. +func newTerminalPrompter() *terminalPrompter { + p := new(terminalPrompter) + // Get the original mode before calling NewLiner. + // This is usually regular "cooked" mode where characters echo. + normalMode, _ := liner.TerminalMode() + // Turn on liner. It switches to raw mode. + p.State = liner.NewLiner() + rawMode, err := liner.TerminalMode() + if err != nil || !liner.TerminalSupported() { + p.supported = false + } else { + p.supported = true + p.normalMode = normalMode + p.rawMode = rawMode + // Switch back to normal mode while we're not prompting. + normalMode.ApplyMode() + } + p.SetCtrlCAborts(true) + p.SetTabCompletionStyle(liner.TabPrints) + p.SetMultiLineMode(true) + return p +} + +// PromptInput displays the given prompt to the user and requests some textual +// data to be entered, returning the input of the user. +func (p *terminalPrompter) PromptInput(prompt string) (string, error) { + if p.supported { + p.rawMode.ApplyMode() + defer p.normalMode.ApplyMode() + } else { + // liner tries to be smart about printing the prompt + // and doesn't print anything if input is redirected. + // Un-smart it by printing the prompt always. + fmt.Print(prompt) + prompt = "" + defer fmt.Println() + } + return p.State.Prompt(prompt) +} + +// PromptPassword displays the given prompt to the user and requests some textual +// data to be entered, but one which must not be echoed out into the terminal. +// The method returns the input provided by the user. +func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) { + if p.supported { + p.rawMode.ApplyMode() + defer p.normalMode.ApplyMode() + return p.State.PasswordPrompt(prompt) + } + if !p.warned { + fmt.Println("!! Unsupported terminal, password will be echoed.") + p.warned = true + } + // Just as in Prompt, handle printing the prompt here instead of relying on liner. + fmt.Print(prompt) + passwd, err = p.State.Prompt("") + fmt.Println() + return passwd, err +} + +// PromptConfirm displays the given prompt to the user and requests a boolean +// choice to be made, returning that choice. +func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { + input, err := p.Prompt(prompt + " [y/n] ") + if len(input) > 0 && strings.EqualFold(input[:1], "y") { + return true, nil + } + return false, err +} + +// SetHistory sets the input scrollback history that the prompter will allow +// the user to scroll back to. +func (p *terminalPrompter) SetHistory(history []string) { + p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) +} + +// AppendHistory appends an entry to the scrollback history. +func (p *terminalPrompter) AppendHistory(command string) { + p.State.AppendHistory(command) +} + +// ClearHistory clears the entire history +func (p *terminalPrompter) ClearHistory() { + p.State.ClearHistory() +} + +// SetWordCompleter sets the completion function that the prompter will call to +// fetch completion candidates when the user presses tab. +func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) { + p.State.SetWordCompleter(liner.WordCompleter(completer)) +} diff --git a/contracts/deposit/AMT/AMT.go b/contracts/deposit/AMT/AMT.go new file mode 100644 index 0000000..3408141 --- /dev/null +++ b/contracts/deposit/AMT/AMT.go @@ -0,0 +1,96 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package amtdeposit + +import ( + "bytes" + "embed" + "github.com/astranetworld/ast/accounts/abi" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "github.com/holiman/uint256" + "github.com/pkg/errors" + "math/big" +) + +//go:embed abi.json +var abiJson embed.FS +var contractAbi abi.ABI +var depositSignature = crypto.Keccak256Hash([]byte("DepositEvent(bytes,uint256,bytes)")) +var withdrawnSignature = crypto.Keccak256Hash([]byte("WithdrawnEvent(uint256)")) + +func init() { + var ( + depositAbiCode []byte + err error + ) + if depositAbiCode, err = abiJson.ReadFile("abi.json"); err != nil { + panic("Could not open abi.json") + } + + if contractAbi, err = abi.JSON(bytes.NewReader(depositAbiCode)); err != nil { + panic("unable to parse AMT deposit contract abi") + } +} + +type Contract struct { +} + +func (Contract) WithdrawnSignature() types.Hash { + return withdrawnSignature +} + +func (Contract) DepositSignature() types.Hash { + return depositSignature +} + +func (c Contract) IsDepositAction(sigdata [4]byte) bool { + var ( + method *abi.Method + err error + ) + if method, err = contractAbi.MethodById(sigdata[:]); err != nil { + return false + } + + if !bytes.Equal(method.ID, contractAbi.Methods["deposit"].ID) { + return false + } + return true +} + +func (Contract) UnpackDepositLogData(data []byte) (publicKey []byte, signature []byte, depositAmount *uint256.Int, err error) { + var ( + unpackedLogs []interface{} + overflow bool + ) + // + if unpackedLogs, err = contractAbi.Unpack("DepositEvent", data); err != nil { + err = errors.Wrap(err, "unable to unpack logs") + return + } + // + if depositAmount, overflow = uint256.FromBig(unpackedLogs[1].(*big.Int)); overflow { + err = errors.New("unable to unpack amount") + return + } + publicKey, signature = unpackedLogs[0].([]byte), unpackedLogs[2].([]byte) + log.Debug("unpacked DepositEvent Logs", "publicKey", hexutil.Encode(unpackedLogs[0].([]byte)), "signature", hexutil.Encode(unpackedLogs[2].([]byte)), "message", hexutil.Encode(depositAmount.Bytes())) + return +} diff --git a/contracts/deposit/AMT/Deposit.sol b/contracts/deposit/AMT/Deposit.sol new file mode 100644 index 0000000..dd46f5e --- /dev/null +++ b/contracts/deposit/AMT/Deposit.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +import "@openzeppelin/contracts@v4.9.0/access/Ownable.sol"; +import "./IDeposit.sol"; + + +contract Deposit is Ownable, IDeposit { + // using Address for address payable; + + mapping(address => uint256) private deposits; + mapping(address => uint64) private depositTime; + uint64 private depositLockingTime; + uint64 private fiftyDepositLimit; + uint64 private oneHundredDepositLimit; + uint64 private fiveHundredDepositLimit; + // + uint256 private allDeposits = 0; + uint64 private fiftyDepositCount = 0; + uint64 private oneHundredDepositCount = 0; + uint64 private fiveHundredDepositCount = 0; + + uint256 constant private fiftyDeposit = 50 ether; + uint256 constant private oneHundredDeposit = 100 ether; + uint256 constant private fiveHundredDeposit = 500 ether; + + + modifier onlyOperator() { + require(msg.sender == owner(), "Caller is not Operator"); + _; + } + + modifier onlyDeposit() { + require(deposits[msg.sender] > 0, "Do not have deposit"); + _; + } + + constructor (uint64 _depositLockingTime, uint64 _fiftyDepositLimit, uint64 _oneHundredDepositLimit, uint64 _fiveHundredDepositLimit) Ownable() { + depositLockingTime = _depositLockingTime; + fiftyDepositLimit = _fiftyDepositLimit; + oneHundredDepositLimit = _oneHundredDepositLimit; + fiveHundredDepositLimit = _fiveHundredDepositLimit; + } + + function depositsOf(address payee) public view override returns (uint256) { + return deposits[payee]; + } + + function depositUnlockingTimestamp(address payee) public view override returns (uint64) { + require(depositTime[payee] > 0, "DepositContract: do not have deposit"); + return depositTime[payee] + depositLockingTime; + } + + function getDepositCount() public view override returns (uint256) { + return allDeposits; + } + + function depositAllowed(uint256 amount) public view returns (bool) { + require(deposits[msg.sender] == 0, "DepositContract: you already deposited"); + require(amount == oneHundredDeposit || amount == fiveHundredDeposit || amount == fiftyDeposit, "DepositContract: payee is not allowed to deposit"); + // + if (amount == fiftyDeposit) { + require(fiftyDepositLimit > fiftyDepositCount, "10 ast Deposit Limit has been reached"); + } + // + if (amount == oneHundredDeposit) { + require(oneHundredDepositLimit > oneHundredDepositCount , "100 ast Deposit Limit has been reached"); + } + // + if (amount == fiveHundredDeposit) { + require(fiveHundredDepositLimit > fiveHundredDepositCount, "500 ast Deposit Limit has been reached"); + } + return true; + } + + function deposit(bytes calldata pubkey, bytes calldata signature) public payable virtual override { + + uint256 amount = msg.value; + require(depositAllowed(amount), "DepositContract: payee is not allowed to deposit"); + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); + require(signature.length == 96, "DepositContract: invalid signature length"); + + // + deposits[msg.sender] = amount; + depositTime[msg.sender] = uint64(block.timestamp); + allDeposits += amount; + // + if (amount == fiftyDeposit) { + fiftyDepositCount++; + } + // + if (amount == oneHundredDeposit) { + oneHundredDepositCount++; + } + // + if (amount == fiveHundredDeposit) { + fiveHundredDepositCount++; + } + + emit DepositEvent(pubkey, amount, signature); + } + + function withdrawalAllowed(uint64 timestamp) public view returns (bool) { + require(deposits[msg.sender] > 0 && depositTime[msg.sender] > 0, "Do not have deposits"); + require(address(this).balance >= deposits[msg.sender], "Insufficient balance"); + require(depositTime[msg.sender] + depositLockingTime <= timestamp, "Deposit is locking"); + return true; + } + + function withdraw() public payable virtual override onlyDeposit { + + require(withdrawalAllowed(uint64(block.timestamp)), "DepositContract: payee is not allowed to deposit"); + uint256 amount = deposits[msg.sender]; + // + sendValue(payable(msg.sender), amount); + delete deposits[msg.sender]; + delete depositTime[msg.sender]; + + // + if (amount == fiftyDeposit) { + fiftyDepositCount--; + } + // + if (amount == oneHundredDeposit) { + oneHundredDepositCount--; + } + // + if (amount == fiveHundredDeposit) { + fiveHundredDepositCount--; + } + + allDeposits -= amount; + emit WithdrawnEvent(amount); + } + + function addDepositLimit(uint256 amount, uint64 limit) public virtual onlyOperator { + // + if (amount == fiftyDeposit) { + fiftyDepositLimit += limit; + } + // + if (amount == oneHundredDeposit) { + oneHundredDepositLimit += limit; + } + // + if (amount == fiveHundredDeposit) { + fiveHundredDepositLimit += limit; + } + } + function getDepositLimit(uint256 amount) public view returns(uint64) { + // + if (amount == fiftyDeposit) { + return fiftyDepositLimit; + } + // + if (amount == oneHundredDeposit) { + return oneHundredDepositLimit; + } + // + if (amount == fiveHundredDeposit) { + return fiveHundredDepositLimit; + } + + return 0; + } + + function getDepositRemain() public view returns(uint64, uint64, uint64) { + uint64 fiftyDepositRemain = fiftyDepositLimit - fiftyDepositCount; + uint64 oneHundredDepositRemain = oneHundredDepositLimit - oneHundredDepositCount; + uint64 fiveHundredDepositRemain = fiveHundredDepositLimit - fiveHundredDepositCount; + return (fiftyDepositRemain, oneHundredDepositRemain, fiveHundredDepositRemain); + } + + function sendValue(address payable recipient, uint256 amount) private { + require(address(this).balance >= amount, "Insufficient balance"); + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Unable to send value, recipient may have reverted"); + } + +} + + diff --git a/contracts/deposit/AMT/IDeposit.sol b/contracts/deposit/AMT/IDeposit.sol new file mode 100644 index 0000000..ed3011f --- /dev/null +++ b/contracts/deposit/AMT/IDeposit.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +interface IDeposit{ + event DepositEvent( + bytes pubkey, + uint256 weiAmount, + bytes signature + ); + event WithdrawnEvent(uint256 weiAmount); + + function deposit( + bytes calldata pubkey, + bytes calldata signature + ) external payable; + + function withdraw() external payable; + function depositsOf(address payee) external view returns (uint256); + function depositUnlockingTimestamp(address payee) external view returns (uint64); + function getDepositCount() external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/deposit/AMT/abi.json b/contracts/deposit/AMT/abi.json new file mode 100644 index 0000000..9e41870 --- /dev/null +++ b/contracts/deposit/AMT/abi.json @@ -0,0 +1,292 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "limit", + "type": "uint64" + } + ], + "name": "addDepositLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_depositLockingTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_fiftyDepositLimit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_oneHundredDepositLimit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_fiveHundredDepositLimit", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weiAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "DepositEvent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "weiAmount", + "type": "uint256" + } + ], + "name": "WithdrawnEvent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "depositAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "payee", + "type": "address" + } + ], + "name": "depositsOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "payee", + "type": "address" + } + ], + "name": "depositUnlockingTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDepositCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "getDepositLimit", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDepositRemain", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + } + ], + "name": "withdrawalAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/deposit/AMT/bytecode.bin b/contracts/deposit/AMT/bytecode.bin new file mode 100644 index 0000000..447ce42 --- /dev/null +++ b/contracts/deposit/AMT/bytecode.bin @@ -0,0 +1 @@ +608060405260006004556000600560006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506000600560086101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506000600560106101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055503480156200009457600080fd5b50604051620026e4380380620026e48339818101604052810190620000ba91906200029e565b620000da620000ce6200018860201b60201c565b6200019060201b60201c565b83600360006101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555082600360086101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555081600360106101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555080600360186101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505050505062000310565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600080fd5b600067ffffffffffffffff82169050919050565b620002788162000259565b81146200028457600080fd5b50565b60008151905062000298816200026d565b92915050565b60008060008060808587031215620002bb57620002ba62000254565b5b6000620002cb8782880162000287565b9450506020620002de8782880162000287565b9350506040620002f18782880162000287565b9250506060620003048782880162000287565b91505092959194509250565b6123c480620003206000396000f3fe6080604052600436106100c25760003560e01c80638da5cb5b1161007f578063d680292f11610059578063d680292f1461024e578063e3a9db1a14610277578063ee7bc641146102b4578063f2fde38b146102e1576100c2565b80638da5cb5b146101bb5780639363a141146101e6578063973bbea014610211576100c2565b80630e4a72ff146100c7578063164af1df146101045780633ccfd60b14610120578063715018a61461012a5780637bf7c807146101415780638d3043b01461017e575b600080fd5b3480156100d357600080fd5b506100ee60048036038101906100e991906115b6565b61030a565b6040516100fb91906115fe565b60405180910390f35b61011e6004803603810190610119919061167e565b6105c0565b005b6101286108bc565b005b34801561013657600080fd5b5061013f610bda565b005b34801561014d57600080fd5b506101686004803603810190610163919061173f565b610bee565b60405161017591906115fe565b60405180910390f35b34801561018a57600080fd5b506101a560048036038101906101a091906115b6565b610e30565b6040516101b2919061177b565b60405180910390f35b3480156101c757600080fd5b506101d0610ec6565b6040516101dd91906117d7565b60405180910390f35b3480156101f257600080fd5b506101fb610eef565b6040516102089190611801565b60405180910390f35b34801561021d57600080fd5b5061023860048036038101906102339190611848565b610ef9565b604051610245919061177b565b60405180910390f35b34801561025a57600080fd5b5061027560048036038101906102709190611875565b611016565b005b34801561028357600080fd5b5061029e60048036038101906102999190611848565b6111a3565b6040516102ab9190611801565b60405180910390f35b3480156102c057600080fd5b506102c96111ec565b6040516102d8939291906118b5565b60405180910390f35b3480156102ed57600080fd5b5061030860048036038101906103039190611848565b6112b5565b005b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461038d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103849061196f565b60405180910390fd5b68056bc75e2d631000008214806103ac5750681b1ae4d6e2ef50000082145b806103bf57506802b5e3af16b188000082145b6103fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f590611a01565b60405180910390fd5b6802b5e3af16b1880000820361049157600560009054906101000a900467ffffffffffffffff1667ffffffffffffffff16600360089054906101000a900467ffffffffffffffff1667ffffffffffffffff1611610490576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161048790611a93565b60405180910390fd5b5b68056bc75e2d63100000820361052457600560089054906101000a900467ffffffffffffffff1667ffffffffffffffff16600360109054906101000a900467ffffffffffffffff1667ffffffffffffffff1611610523576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161051a90611b25565b60405180910390fd5b5b681b1ae4d6e2ef50000082036105b757600560109054906101000a900467ffffffffffffffff1667ffffffffffffffff16600360189054906101000a900467ffffffffffffffff1667ffffffffffffffff16116105b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ad90611bb7565b60405180910390fd5b5b60019050919050565b60003490506105ce8161030a565b61060d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060490611a01565b60405180910390fd5b60308585905014610653576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161064a90611c49565b60405180910390fd5b60608383905014610699576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161069090611cdb565b60405180910390fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555042600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555080600460008282546107559190611d2a565b925050819055506802b5e3af16b188000081036107ba576005600081819054906101000a900467ffffffffffffffff168092919061079290611d5e565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b68056bc75e2d631000008103610818576005600881819054906101000a900467ffffffffffffffff16809291906107f090611d5e565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b681b1ae4d6e2ef5000008103610876576005601081819054906101000a900467ffffffffffffffff168092919061084e90611d5e565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b7fb8d51a6e65c24b720301201860c41bedb147444aa7a59c1c6402493d56595f5885858386866040516108ad959493929190611dec565b60405180910390a15050505050565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541161093e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093590611e81565b60405180910390fd5b61094742610bee565b610986576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097d90611a01565b60405180910390fd5b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506109d43382611338565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009055600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81549067ffffffffffffffff02191690556802b5e3af16b18800008103610acb576005600081819054906101000a900467ffffffffffffffff1680929190610aa390611ea1565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b68056bc75e2d631000008103610b29576005600881819054906101000a900467ffffffffffffffff1680929190610b0190611ea1565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b681b1ae4d6e2ef5000008103610b87576005601081819054906101000a900467ffffffffffffffff1680929190610b5f90611ea1565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550505b8060046000828254610b999190611eca565b925050819055507f58799390987ee1f6c91da03f668142478dbf60938ef1355203cceb37caea106b81604051610bcf9190611801565b60405180910390a150565b610be261142c565b610bec60006114aa565b565b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054118015610c9b57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900467ffffffffffffffff1667ffffffffffffffff16115b610cda576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cd190611f4a565b60405180910390fd5b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054471015610d5c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d5390611fb6565b60405180910390fd5b8167ffffffffffffffff16600360009054906101000a900467ffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900467ffffffffffffffff16610ddc9190611fd6565b67ffffffffffffffff161115610e27576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e1e9061205e565b60405180910390fd5b60019050919050565b60006802b5e3af16b18800008203610e6057600360089054906101000a900467ffffffffffffffff169050610ec1565b68056bc75e2d631000008203610e8e57600360109054906101000a900467ffffffffffffffff169050610ec1565b681b1ae4d6e2ef5000008203610ebc57600360189054906101000a900467ffffffffffffffff169050610ec1565b600090505b919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000600454905090565b600080600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900467ffffffffffffffff1667ffffffffffffffff1611610f9a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f91906120f0565b60405180910390fd5b600360009054906101000a900467ffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900467ffffffffffffffff1661100f9190611fd6565b9050919050565b61101e610ec6565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461108b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110829061215c565b60405180910390fd5b6802b5e3af16b188000082036110e75780600360088282829054906101000a900467ffffffffffffffff166110c09190611fd6565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505b68056bc75e2d6310000082036111435780600360108282829054906101000a900467ffffffffffffffff1661111c9190611fd6565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505b681b1ae4d6e2ef500000820361119f5780600360188282829054906101000a900467ffffffffffffffff166111789190611fd6565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505b5050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080600080600560009054906101000a900467ffffffffffffffff16600360089054906101000a900467ffffffffffffffff1661122a919061217c565b90506000600560089054906101000a900467ffffffffffffffff16600360109054906101000a900467ffffffffffffffff16611266919061217c565b90506000600560109054906101000a900467ffffffffffffffff16600360189054906101000a900467ffffffffffffffff166112a2919061217c565b9050828282955095509550505050909192565b6112bd61142c565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361132c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113239061222a565b60405180910390fd5b611335816114aa565b50565b8047101561137b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161137290611fb6565b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff16826040516113a19061227b565b60006040518083038185875af1925050503d80600081146113de576040519150601f19603f3d011682016040523d82523d6000602084013e6113e3565b606091505b5050905080611427576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161141e90612302565b60405180910390fd5b505050565b61143461156e565b73ffffffffffffffffffffffffffffffffffffffff16611452610ec6565b73ffffffffffffffffffffffffffffffffffffffff16146114a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161149f9061236e565b60405180910390fd5b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600033905090565b600080fd5b600080fd5b6000819050919050565b61159381611580565b811461159e57600080fd5b50565b6000813590506115b08161158a565b92915050565b6000602082840312156115cc576115cb611576565b5b60006115da848285016115a1565b91505092915050565b60008115159050919050565b6115f8816115e3565b82525050565b600060208201905061161360008301846115ef565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261163e5761163d611619565b5b8235905067ffffffffffffffff81111561165b5761165a61161e565b5b60208301915083600182028301111561167757611676611623565b5b9250929050565b6000806000806040858703121561169857611697611576565b5b600085013567ffffffffffffffff8111156116b6576116b561157b565b5b6116c287828801611628565b9450945050602085013567ffffffffffffffff8111156116e5576116e461157b565b5b6116f187828801611628565b925092505092959194509250565b600067ffffffffffffffff82169050919050565b61171c816116ff565b811461172757600080fd5b50565b60008135905061173981611713565b92915050565b60006020828403121561175557611754611576565b5b60006117638482850161172a565b91505092915050565b611775816116ff565b82525050565b6000602082019050611790600083018461176c565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006117c182611796565b9050919050565b6117d1816117b6565b82525050565b60006020820190506117ec60008301846117c8565b92915050565b6117fb81611580565b82525050565b600060208201905061181660008301846117f2565b92915050565b611825816117b6565b811461183057600080fd5b50565b6000813590506118428161181c565b92915050565b60006020828403121561185e5761185d611576565b5b600061186c84828501611833565b91505092915050565b6000806040838503121561188c5761188b611576565b5b600061189a858286016115a1565b92505060206118ab8582860161172a565b9150509250929050565b60006060820190506118ca600083018661176c565b6118d7602083018561176c565b6118e4604083018461176c565b949350505050565b600082825260208201905092915050565b7f4465706f736974436f6e74726163743a20796f7520616c72656164792064657060008201527f6f73697465640000000000000000000000000000000000000000000000000000602082015250565b60006119596026836118ec565b9150611964826118fd565b604082019050919050565b600060208201905081810360008301526119888161194c565b9050919050565b7f4465706f736974436f6e74726163743a207061796565206973206e6f7420616c60008201527f6c6f77656420746f206465706f73697400000000000000000000000000000000602082015250565b60006119eb6030836118ec565b91506119f68261198f565b604082019050919050565b60006020820190508181036000830152611a1a816119de565b9050919050565b7f313020414d43204465706f736974204c696d697420686173206265656e20726560008201527f6163686564000000000000000000000000000000000000000000000000000000602082015250565b6000611a7d6025836118ec565b9150611a8882611a21565b604082019050919050565b60006020820190508181036000830152611aac81611a70565b9050919050565b7f31303020414d43204465706f736974204c696d697420686173206265656e207260008201527f6561636865640000000000000000000000000000000000000000000000000000602082015250565b6000611b0f6026836118ec565b9150611b1a82611ab3565b604082019050919050565b60006020820190508181036000830152611b3e81611b02565b9050919050565b7f35303020414d43204465706f736974204c696d697420686173206265656e207260008201527f6561636865640000000000000000000000000000000000000000000000000000602082015250565b6000611ba16026836118ec565b9150611bac82611b45565b604082019050919050565b60006020820190508181036000830152611bd081611b94565b9050919050565b7f4465706f736974436f6e74726163743a20696e76616c6964207075626b65792060008201527f6c656e6774680000000000000000000000000000000000000000000000000000602082015250565b6000611c336026836118ec565b9150611c3e82611bd7565b604082019050919050565b60006020820190508181036000830152611c6281611c26565b9050919050565b7f4465706f736974436f6e74726163743a20696e76616c6964207369676e61747560008201527f7265206c656e6774680000000000000000000000000000000000000000000000602082015250565b6000611cc56029836118ec565b9150611cd082611c69565b604082019050919050565b60006020820190508181036000830152611cf481611cb8565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000611d3582611580565b9150611d4083611580565b9250828201905080821115611d5857611d57611cfb565b5b92915050565b6000611d69826116ff565b915067ffffffffffffffff8203611d8357611d82611cfb565b5b600182019050919050565b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b6000611dcb8385611d8e565b9350611dd8838584611d9f565b611de183611dae565b840190509392505050565b60006060820190508181036000830152611e07818789611dbf565b9050611e1660208301866117f2565b8181036040830152611e29818486611dbf565b90509695505050505050565b7f446f206e6f742068617665206465706f73697400000000000000000000000000600082015250565b6000611e6b6013836118ec565b9150611e7682611e35565b602082019050919050565b60006020820190508181036000830152611e9a81611e5e565b9050919050565b6000611eac826116ff565b915060008203611ebf57611ebe611cfb565b5b600182039050919050565b6000611ed582611580565b9150611ee083611580565b9250828203905081811115611ef857611ef7611cfb565b5b92915050565b7f446f206e6f742068617665206465706f73697473000000000000000000000000600082015250565b6000611f346014836118ec565b9150611f3f82611efe565b602082019050919050565b60006020820190508181036000830152611f6381611f27565b9050919050565b7f496e73756666696369656e742062616c616e6365000000000000000000000000600082015250565b6000611fa06014836118ec565b9150611fab82611f6a565b602082019050919050565b60006020820190508181036000830152611fcf81611f93565b9050919050565b6000611fe1826116ff565b9150611fec836116ff565b9250828201905067ffffffffffffffff81111561200c5761200b611cfb565b5b92915050565b7f4465706f736974206973206c6f636b696e670000000000000000000000000000600082015250565b60006120486012836118ec565b915061205382612012565b602082019050919050565b600060208201905081810360008301526120778161203b565b9050919050565b7f4465706f736974436f6e74726163743a20646f206e6f7420686176652064657060008201527f6f73697400000000000000000000000000000000000000000000000000000000602082015250565b60006120da6024836118ec565b91506120e58261207e565b604082019050919050565b60006020820190508181036000830152612109816120cd565b9050919050565b7f43616c6c6572206973206e6f74204f70657261746f7200000000000000000000600082015250565b60006121466016836118ec565b915061215182612110565b602082019050919050565b6000602082019050818103600083015261217581612139565b9050919050565b6000612187826116ff565b9150612192836116ff565b9250828203905067ffffffffffffffff8111156121b2576121b1611cfb565b5b92915050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b60006122146026836118ec565b915061221f826121b8565b604082019050919050565b6000602082019050818103600083015261224381612207565b9050919050565b600081905092915050565b50565b600061226560008361224a565b915061227082612255565b600082019050919050565b600061228682612258565b9150819050919050565b7f556e61626c6520746f2073656e642076616c75652c20726563697069656e742060008201527f6d61792068617665207265766572746564000000000000000000000000000000602082015250565b60006122ec6031836118ec565b91506122f782612290565b604082019050919050565b6000602082019050818103600083015261231b816122df565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b60006123586020836118ec565b915061236382612322565b602082019050919050565b600060208201905081810360008301526123878161234b565b905091905056fea2646970667358221220b62c2fec2b83d4d4dfd96ba7114dc2e4110afaa2e05b2f08d56984d0693be50464736f6c63430008110033 \ No newline at end of file diff --git a/contracts/deposit/FUJI/FUJI.go b/contracts/deposit/FUJI/FUJI.go new file mode 100644 index 0000000..3e19c6d --- /dev/null +++ b/contracts/deposit/FUJI/FUJI.go @@ -0,0 +1,96 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package fujideposit + +import ( + "bytes" + "embed" + "github.com/astranetworld/ast/accounts/abi" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "github.com/holiman/uint256" + "github.com/pkg/errors" + "math/big" +) + +//go:embed abi.json +var abiJson embed.FS +var contractAbi abi.ABI +var depositSignature = crypto.Keccak256Hash([]byte("DepositEvent(bytes,uint256,bytes)")) +var withdrawnSignature = crypto.Keccak256Hash([]byte("WithdrawnEvent(uint256)")) + +func init() { + var ( + depositAbiCode []byte + err error + ) + if depositAbiCode, err = abiJson.ReadFile("abi.json"); err != nil { + panic("Could not open abi.json") + } + + if contractAbi, err = abi.JSON(bytes.NewReader(depositAbiCode)); err != nil { + panic("unable to parse AMT deposit contract abi") + } +} + +type Contract struct { +} + +func (c Contract) IsDepositAction(sigdata [4]byte) bool { + var ( + method *abi.Method + err error + ) + if method, err = contractAbi.MethodById(sigdata[:]); err != nil { + return false + } + + if !bytes.Equal(method.ID, contractAbi.Methods["deposit"].ID) { + return false + } + return true +} + +func (Contract) WithdrawnSignature() types.Hash { + return withdrawnSignature +} + +func (Contract) DepositSignature() types.Hash { + return depositSignature +} + +func (Contract) UnpackDepositLogData(data []byte) (publicKey []byte, signature []byte, depositAmount *uint256.Int, err error) { + var ( + unpackedLogs []interface{} + overflow bool + ) + // + if unpackedLogs, err = contractAbi.Unpack("DepositEvent", data); err != nil { + err = errors.Wrap(err, "unable to unpack logs") + return + } + // + if depositAmount, overflow = uint256.FromBig(unpackedLogs[1].(*big.Int)); overflow { + err = errors.New("unable to unpack amount") + return + } + publicKey, signature = unpackedLogs[0].([]byte), unpackedLogs[2].([]byte) + log.Debug("unpacked DepositEvent Logs", "publicKey", hexutil.Encode(unpackedLogs[0].([]byte)), "signature", hexutil.Encode(unpackedLogs[2].([]byte)), "message", hexutil.Encode(depositAmount.Bytes())) + return +} diff --git a/contracts/deposit/FUJI/IDeposit.sol b/contracts/deposit/FUJI/IDeposit.sol new file mode 100644 index 0000000..e48fc11 --- /dev/null +++ b/contracts/deposit/FUJI/IDeposit.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface IDeposit { + event DepositEvent(bytes pubkey, uint256 weiAmount, bytes signature); + + event WithdrawnEvent(uint256 weiAmount); + + function deposit( + bytes calldata pubkey, + bytes calldata signature, + uint256 tokenID + ) external; + + function withdraw() external; + + function depositsOf(address account) external view returns (uint256); + + function depositUnlockingTimestamp( + address payee + ) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/deposit/FUJI/abi.json b/contracts/deposit/FUJI/abi.json new file mode 100644 index 0000000..e29334e --- /dev/null +++ b/contracts/deposit/FUJI/abi.json @@ -0,0 +1,366 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "fujiAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "fujiAdminAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weiAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "DepositEvent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "weiAmount", + "type": "uint256" + } + ], + "name": "WithdrawnEvent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "T2000TokenIDs", + "type": "uint256[]" + } + ], + "name": "addT2000TokendIDs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "T200TokenIDs", + "type": "uint256[]" + } + ], + "name": "addT200TokendIDs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "T800TokenIDs", + "type": "uint256[]" + } + ], + "name": "addT800TokendIDs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "tokenID", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "depositUnlockingTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "depositsOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "depositsOfBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenID", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOfBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "withdrawOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/deposit/FUJI/staking.sol b/contracts/deposit/FUJI/staking.sol new file mode 100644 index 0000000..10cc999 --- /dev/null +++ b/contracts/deposit/FUJI/staking.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts@v4.9.0/access/Ownable.sol"; +import "@openzeppelin/contracts@v4.9.0/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts@v4.9.0/token/ERC721/IERC721Receiver.sol"; +import "@openzeppelin/contracts@v4.9.0/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-upgradeable@v4.9.0/proxy/utils/Initializable.sol"; +import "./IDeposit.sol"; + +contract StakingFUJI is IDeposit, IERC721Receiver, Ownable { + IERC721 token; + mapping(uint256 => bool) T200NFT; + mapping(uint256 => bool) T800NFT; + mapping(uint256 => bool) T2000NFT; + + mapping(address => uint256) depoistNFT; + mapping(address => uint256) depositExpire; + + mapping(uint256 => address) withdrawIDToAddress; + mapping(address => uint256) withdrawAddressToID; + // mapping(address => uint256) withdrawNFT; + + constructor(address fujiAddr, address fujiAdminAddress) Ownable() { + token = IERC721(fujiAddr); + token.setApprovalForAll(fujiAdminAddress, true); + } + + function addT200TokendIDs(uint256[] calldata T200TokenIDs) public virtual onlyOwner { + for (uint i = 0; i < T200TokenIDs.length; i++) { + T200NFT[T200TokenIDs[i]] = true; + } + } + + function addT800TokendIDs(uint256[] calldata T800TokenIDs) public virtual onlyOwner { + for (uint i = 0; i < T800TokenIDs.length; i++) { + T800NFT[T800TokenIDs[i]] = true; + } + } + + function addT2000TokendIDs(uint256[] calldata T2000TokenIDs) public virtual onlyOwner { + for (uint i = 0; i < T2000TokenIDs.length; i++) { + T2000NFT[T2000TokenIDs[i]] = true; + } + } + + function supportsInterface( + bytes4 interfaceId + ) external pure returns (bool) { + return + interfaceId == type(IDeposit).interfaceId || + interfaceId == type(IERC721Receiver).interfaceId; + } + + function deposit( + bytes calldata pubkey, + bytes calldata signature, + uint256 tokenID + ) external { + require(pubkey.length == 48, "Staking: invalid public key"); + require(signature.length == 96, "Staking: invalid signature"); + require( + T200NFT[tokenID] || T800NFT[tokenID] || T2000NFT[tokenID], + "Staking: not allowed token" + ); + require(depoistNFT[msg.sender] == tokenID, "Staking: tokenID do not have transfed"); + + depositExpire[msg.sender] = block.timestamp + 90 days; + emit DepositEvent(pubkey, nftToAmount(tokenID), signature); + } + + function withdraw() external { + + uint256 tokenID = depoistNFT[msg.sender]; + + require(tokenID > 0, "Staking: not deposited"); + require( + depositExpire[msg.sender] <= block.timestamp, + "Staking: token is locked" + ); + require( + token.ownerOf(tokenID) == address(this), + "Staking: Insuficient Allowance" + ); + + withdrawIDToAddress[tokenID] = msg.sender; + withdrawAddressToID[msg.sender] = tokenID; + // withdrawNFT[msg.sender] = tokenID; + delete depoistNFT[msg.sender]; + delete depositExpire[msg.sender]; + + emit WithdrawnEvent(nftToAmount(tokenID)); + } + + function depositsOf(address account) external view returns (uint256) { + if (depositExpire[account] == 0) { + return 0; + } + return depoistNFT[account]; + } + + function depositsOfBalance(address account) external view returns (uint256) { + if (depositExpire[account] == 0) { + return 0; + } + return nftToAmount(depoistNFT[account]); + } + function transferOf(address account) external view returns (uint256) { + return depoistNFT[account]; + } + + function transferOfBalance(address account) external view returns (uint256) { + return nftToAmount(depoistNFT[account]); + } + + function depositUnlockingTimestamp( + address account + ) external view returns (uint256) { + return depositExpire[account]; + } + + function withdrawOf(address account) external view returns (uint256) { + return withdrawAddressToID[account]; + } + + + function nftToAmount(uint256 id) internal view returns (uint256) { + if (T200NFT[id] == true) { + return 200 ether; + } + + if (T800NFT[id] == true) { + return 800 ether; + } + + if (T2000NFT[id] == true) { + return 2000 ether; + } + + return 0 ether; + } + + function onERC721Received( + address /*operator*/, + address sender /*from*/, + uint256 tokenID /*id*/, + bytes calldata /*data*/ + ) external returns (bytes4) { + require( + T200NFT[tokenID] || T800NFT[tokenID] || T2000NFT[tokenID], + "Staking: not allowed token" + ); + + // A deposit T1 , withdraw T1, A send T1 to B, B deposit T1, A deposit T2 + if (withdrawIDToAddress[tokenID] != address(0)) { + address withdrawAddress = withdrawIDToAddress[tokenID]; + delete withdrawAddressToID[withdrawAddress]; + delete withdrawIDToAddress[tokenID]; + } + + + if (withdrawAddressToID[sender] > 0) { +// require( +// token.ownerOf(withdrawAddressToID[sender]) != address(this), +// "Staking: Have not withdraw" +// ); + + uint256 withdrawTokenID = withdrawAddressToID[sender]; + delete withdrawAddressToID[sender]; + delete withdrawIDToAddress[withdrawTokenID]; + } + + depoistNFT[sender] = tokenID; + return + bytes4( + keccak256( + "onERC721Received(address,address,uint256,bytes)" + ) + ); + } +} \ No newline at end of file diff --git a/contracts/deposit/NFT/IDeposit.sol b/contracts/deposit/NFT/IDeposit.sol new file mode 100644 index 0000000..e48fc11 --- /dev/null +++ b/contracts/deposit/NFT/IDeposit.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface IDeposit { + event DepositEvent(bytes pubkey, uint256 weiAmount, bytes signature); + + event WithdrawnEvent(uint256 weiAmount); + + function deposit( + bytes calldata pubkey, + bytes calldata signature, + uint256 tokenID + ) external; + + function withdraw() external; + + function depositsOf(address account) external view returns (uint256); + + function depositUnlockingTimestamp( + address payee + ) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/deposit/NFT/NFT.go b/contracts/deposit/NFT/NFT.go new file mode 100644 index 0000000..12af999 --- /dev/null +++ b/contracts/deposit/NFT/NFT.go @@ -0,0 +1,96 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package nftdeposit + +import ( + "bytes" + "embed" + "github.com/astranetworld/ast/accounts/abi" + "github.com/astranetworld/ast/common/crypto" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + "github.com/holiman/uint256" + "github.com/pkg/errors" + "math/big" +) + +//go:embed abi.json +var abiJson embed.FS +var contractAbi abi.ABI +var depositSignature = crypto.Keccak256Hash([]byte("DepositEvent(bytes,uint256,bytes)")) +var withdrawnSignature = crypto.Keccak256Hash([]byte("WithdrawnEvent(uint256)")) + +func init() { + var ( + depositAbiCode []byte + err error + ) + if depositAbiCode, err = abiJson.ReadFile("abi.json"); err != nil { + panic("Could not open abi.json") + } + + if contractAbi, err = abi.JSON(bytes.NewReader(depositAbiCode)); err != nil { + panic("unable to parse AMT deposit contract abi") + } +} + +type Contract struct { +} + +func (c Contract) IsDepositAction(sigdata [4]byte) bool { + var ( + method *abi.Method + err error + ) + if method, err = contractAbi.MethodById(sigdata[:]); err != nil { + return false + } + + if !bytes.Equal(method.ID, contractAbi.Methods["deposit"].ID) { + return false + } + return true +} + +func (Contract) WithdrawnSignature() types.Hash { + return withdrawnSignature +} + +func (Contract) DepositSignature() types.Hash { + return depositSignature +} + +func (Contract) UnpackDepositLogData(data []byte) (publicKey []byte, signature []byte, depositAmount *uint256.Int, err error) { + var ( + unpackedLogs []interface{} + overflow bool + ) + // + if unpackedLogs, err = contractAbi.Unpack("DepositEvent", data); err != nil { + err = errors.Wrap(err, "unable to unpack logs") + return + } + // + if depositAmount, overflow = uint256.FromBig(unpackedLogs[1].(*big.Int)); overflow { + err = errors.New("unable to unpack amount") + return + } + publicKey, signature = unpackedLogs[0].([]byte), unpackedLogs[2].([]byte) + log.Debug("unpacked DepositEvent Logs", "publicKey", hexutil.Encode(unpackedLogs[0].([]byte)), "signature", hexutil.Encode(unpackedLogs[2].([]byte)), "message", hexutil.Encode(depositAmount.Bytes())) + return +} diff --git a/contracts/deposit/NFT/abi.json b/contracts/deposit/NFT/abi.json new file mode 100644 index 0000000..754db41 --- /dev/null +++ b/contracts/deposit/NFT/abi.json @@ -0,0 +1,246 @@ +[ + { + "inputs":[ + + ], + "stateMutability":"nonpayable", + "type":"constructor" + }, + { + "anonymous":false, + "inputs":[ + { + "indexed":false, + "internalType":"bytes", + "name":"pubkey", + "type":"bytes" + }, + { + "indexed":false, + "internalType":"uint256", + "name":"weiAmount", + "type":"uint256" + }, + { + "indexed":false, + "internalType":"bytes", + "name":"signature", + "type":"bytes" + } + ], + "name":"DepositEvent", + "type":"event" + }, + { + "anonymous":false, + "inputs":[ + { + "indexed":false, + "internalType":"uint8", + "name":"version", + "type":"uint8" + } + ], + "name":"Initialized", + "type":"event" + }, + { + "anonymous":false, + "inputs":[ + { + "indexed":false, + "internalType":"uint256", + "name":"weiAmount", + "type":"uint256" + } + ], + "name":"WithdrawnEvent", + "type":"event" + }, + { + "inputs":[ + { + "internalType":"bytes", + "name":"pubkey", + "type":"bytes" + }, + { + "internalType":"bytes", + "name":"signature", + "type":"bytes" + }, + { + "internalType":"uint256", + "name":"tokenID", + "type":"uint256" + } + ], + "name":"deposit", + "outputs":[ + + ], + "stateMutability":"nonpayable", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"address", + "name":"account", + "type":"address" + } + ], + "name":"depositUnlockingTimestamp", + "outputs":[ + { + "internalType":"uint256", + "name":"", + "type":"uint256" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"address", + "name":"account", + "type":"address" + } + ], + "name":"depositsOf", + "outputs":[ + { + "internalType":"uint256", + "name":"", + "type":"uint256" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"address", + "name":"addr", + "type":"address" + } + ], + "name":"initialize", + "outputs":[ + + ], + "stateMutability":"nonpayable", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"address", + "name":"", + "type":"address" + }, + { + "internalType":"address", + "name":"", + "type":"address" + }, + { + "internalType":"uint256[]", + "name":"", + "type":"uint256[]" + }, + { + "internalType":"uint256[]", + "name":"", + "type":"uint256[]" + }, + { + "internalType":"bytes", + "name":"", + "type":"bytes" + } + ], + "name":"onERC1155BatchReceived", + "outputs":[ + { + "internalType":"bytes4", + "name":"", + "type":"bytes4" + } + ], + "stateMutability":"nonpayable", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"address", + "name":"", + "type":"address" + }, + { + "internalType":"address", + "name":"", + "type":"address" + }, + { + "internalType":"uint256", + "name":"", + "type":"uint256" + }, + { + "internalType":"uint256", + "name":"", + "type":"uint256" + }, + { + "internalType":"bytes", + "name":"", + "type":"bytes" + } + ], + "name":"onERC1155Received", + "outputs":[ + { + "internalType":"bytes4", + "name":"", + "type":"bytes4" + } + ], + "stateMutability":"pure", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"bytes4", + "name":"interfaceId", + "type":"bytes4" + } + ], + "name":"supportsInterface", + "outputs":[ + { + "internalType":"bool", + "name":"", + "type":"bool" + } + ], + "stateMutability":"pure", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"withdraw", + "outputs":[ + + ], + "stateMutability":"nonpayable", + "type":"function" + } +] \ No newline at end of file diff --git a/contracts/deposit/NFT/bytecode.bin b/contracts/deposit/NFT/bytecode.bin new file mode 100644 index 0000000..4b30c52 --- /dev/null +++ b/contracts/deposit/NFT/bytecode.bin @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b506040518060200160405280600081525062000033816200005a60201b60201c565b5062000054620000486200006f60201b60201c565b6200007760201b60201c565b6200049e565b80600290816200006b9190620003b7565b5050565b600033905090565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620001bf57607f821691505b602082108103620001d557620001d462000177565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026200023f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000200565b6200024b868362000200565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000298620002926200028c8462000263565b6200026d565b62000263565b9050919050565b6000819050919050565b620002b48362000277565b620002cc620002c3826200029f565b8484546200020d565b825550505050565b600090565b620002e3620002d4565b620002f0818484620002a9565b505050565b5b8181101562000318576200030c600082620002d9565b600181019050620002f6565b5050565b601f82111562000367576200033181620001db565b6200033c84620001f0565b810160208510156200034c578190505b620003646200035b85620001f0565b830182620002f5565b50505b505050565b600082821c905092915050565b60006200038c600019846008026200036c565b1980831691505092915050565b6000620003a7838362000379565b9150826002028217905092915050565b620003c2826200013d565b67ffffffffffffffff811115620003de57620003dd62000148565b5b620003ea8254620001a6565b620003f78282856200031c565b600060209050601f8311600181146200042f57600084156200041a578287015190505b62000426858262000399565b86555062000496565b601f1984166200043f86620001db565b60005b82811015620004695784890151825560018201915060208501945060208101905062000442565b8683101562000489578489015162000485601f89168262000379565b8355505b6001600288020188555050505b505050505050565b612b3880620004ae6000396000f3fe608060405234801561001057600080fd5b50600436106100b35760003560e01c8063715018a611610071578063715018a6146101b05780638da5cb5b146101ba578063a22cb465146101d8578063e985e9c5146101f4578063f242432a14610224578063f2fde38b14610240576100b3565b8062fdd58e146100b857806301ffc9a7146100e85780630e89341c146101185780632eb2c2d61461014857806340c10f19146101645780634e1273f414610180575b600080fd5b6100d260048036038101906100cd9190611819565b61025c565b6040516100df9190611868565b60405180910390f35b61010260048036038101906100fd91906118db565b610324565b60405161010f9190611923565b60405180910390f35b610132600480360381019061012d919061193e565b610406565b60405161013f91906119fb565b60405180910390f35b610162600480360381019061015d9190611c1a565b61049a565b005b61017e60048036038101906101799190611819565b61053b565b005b61019a60048036038101906101959190611dac565b610563565b6040516101a79190611ee2565b60405180910390f35b6101b861067c565b005b6101c2610690565b6040516101cf9190611f13565b60405180910390f35b6101f260048036038101906101ed9190611f5a565b6106ba565b005b61020e60048036038101906102099190611f9a565b6106d0565b60405161021b9190611923565b60405180910390f35b61023e60048036038101906102399190611fda565b610764565b005b61025a60048036038101906102559190612071565b610805565b005b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036102cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c390612110565b60405180910390fd5b60008083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60007fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ef57507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ff57506103fe82610888565b5b9050919050565b6060600280546104159061215f565b80601f01602080910402602001604051908101604052809291908181526020018280546104419061215f565b801561048e5780601f106104635761010080835404028352916020019161048e565b820191906000526020600020905b81548152906001019060200180831161047157829003601f168201915b50505050509050919050565b6104a26108f2565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614806104e857506104e7856104e26108f2565b6106d0565b5b610527576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161051e90612202565b60405180910390fd5b61053485858585856108fa565b5050505050565b610543610c1b565b61055f8282600160405180602001604052806000815250610c99565b5050565b606081518351146105a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a090612294565b60405180910390fd5b6000835167ffffffffffffffff8111156105c6576105c5611a22565b5b6040519080825280602002602001820160405280156105f45781602001602082028036833780820191505090505b50905060005b845181101561067157610641858281518110610619576106186122b4565b5b6020026020010151858381518110610634576106336122b4565b5b602002602001015161025c565b828281518110610654576106536122b4565b5b6020026020010181815250508061066a90612312565b90506105fa565b508091505092915050565b610684610c1b565b61068e6000610e49565b565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6106cc6106c56108f2565b8383610f0f565b5050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61076c6108f2565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614806107b257506107b1856107ac6108f2565b6106d0565b5b6107f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107e890612202565b60405180910390fd5b6107fe858585858561107b565b5050505050565b61080d610c1b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361087c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610873906123cc565b60405180910390fd5b61088581610e49565b50565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600033905090565b815183511461093e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109359061245e565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a4906124f0565b60405180910390fd5b60006109b76108f2565b90506109c7818787878787611316565b60005b8451811015610b785760008582815181106109e8576109e76122b4565b5b602002602001015190506000858381518110610a0757610a066122b4565b5b60200260200101519050600080600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610aa8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a9f90612582565b60405180910390fd5b81810360008085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508160008085815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b5d91906125a2565b9250508190555050505080610b7190612312565b90506109ca565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051610bef9291906125d6565b60405180910390a4610c0581878787878761131e565b610c13818787878787611326565b505050505050565b610c236108f2565b73ffffffffffffffffffffffffffffffffffffffff16610c41610690565b73ffffffffffffffffffffffffffffffffffffffff1614610c97576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c8e90612659565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610d08576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cff906126eb565b60405180910390fd5b6000610d126108f2565b90506000610d1f856114fd565b90506000610d2c856114fd565b9050610d3d83600089858589611316565b8460008088815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610d9c91906125a2565b925050819055508673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628989604051610e1a92919061270b565b60405180910390a4610e318360008985858961131e565b610e4083600089898989611577565b50505050505050565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610f7d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f74906127a6565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405161106e9190611923565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036110ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110e1906124f0565b60405180910390fd5b60006110f46108f2565b90506000611101856114fd565b9050600061110e856114fd565b905061111e838989858589611316565b600080600088815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050858110156111b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111ac90612582565b60405180910390fd5b85810360008089815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508560008089815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461126a91906125a2565b925050819055508773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628a8a6040516112e792919061270b565b60405180910390a46112fd848a8a86868a61131e565b61130b848a8a8a8a8a611577565b505050505050505050565b505050505050565b505050505050565b6113458473ffffffffffffffffffffffffffffffffffffffff1661174e565b156114f5578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b815260040161138b95949392919061281b565b6020604051808303816000875af19250505080156113c757506040513d601f19601f820116820180604052508101906113c49190612898565b60015b61146c576113d36128d2565b806308c379a00361142f57506113e76128f4565b806113f25750611431565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142691906119fb565b60405180910390fd5b505b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611463906129f6565b60405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146114f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114ea90612a88565b60405180910390fd5b505b505050505050565b60606000600167ffffffffffffffff81111561151c5761151b611a22565b5b60405190808252806020026020018201604052801561154a5781602001602082028036833780820191505090505b5090508281600081518110611562576115616122b4565b5b60200260200101818152505080915050919050565b6115968473ffffffffffffffffffffffffffffffffffffffff1661174e565b15611746578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016115dc959493929190612aa8565b6020604051808303816000875af192505050801561161857506040513d601f19601f820116820180604052508101906116159190612898565b60015b6116bd576116246128d2565b806308c379a00361168057506116386128f4565b806116435750611682565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161167791906119fb565b60405180910390fd5b505b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b4906129f6565b60405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614611744576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161173b90612a88565b60405180910390fd5b505b505050505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006117b082611785565b9050919050565b6117c0816117a5565b81146117cb57600080fd5b50565b6000813590506117dd816117b7565b92915050565b6000819050919050565b6117f6816117e3565b811461180157600080fd5b50565b600081359050611813816117ed565b92915050565b600080604083850312156118305761182f61177b565b5b600061183e858286016117ce565b925050602061184f85828601611804565b9150509250929050565b611862816117e3565b82525050565b600060208201905061187d6000830184611859565b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6118b881611883565b81146118c357600080fd5b50565b6000813590506118d5816118af565b92915050565b6000602082840312156118f1576118f061177b565b5b60006118ff848285016118c6565b91505092915050565b60008115159050919050565b61191d81611908565b82525050565b60006020820190506119386000830184611914565b92915050565b6000602082840312156119545761195361177b565b5b600061196284828501611804565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156119a557808201518184015260208101905061198a565b60008484015250505050565b6000601f19601f8301169050919050565b60006119cd8261196b565b6119d78185611976565b93506119e7818560208601611987565b6119f0816119b1565b840191505092915050565b60006020820190508181036000830152611a1581846119c2565b905092915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b611a5a826119b1565b810181811067ffffffffffffffff82111715611a7957611a78611a22565b5b80604052505050565b6000611a8c611771565b9050611a988282611a51565b919050565b600067ffffffffffffffff821115611ab857611ab7611a22565b5b602082029050602081019050919050565b600080fd5b6000611ae1611adc84611a9d565b611a82565b90508083825260208201905060208402830185811115611b0457611b03611ac9565b5b835b81811015611b2d5780611b198882611804565b845260208401935050602081019050611b06565b5050509392505050565b600082601f830112611b4c57611b4b611a1d565b5b8135611b5c848260208601611ace565b91505092915050565b600080fd5b600067ffffffffffffffff821115611b8557611b84611a22565b5b611b8e826119b1565b9050602081019050919050565b82818337600083830152505050565b6000611bbd611bb884611b6a565b611a82565b905082815260208101848484011115611bd957611bd8611b65565b5b611be4848285611b9b565b509392505050565b600082601f830112611c0157611c00611a1d565b5b8135611c11848260208601611baa565b91505092915050565b600080600080600060a08688031215611c3657611c3561177b565b5b6000611c44888289016117ce565b9550506020611c55888289016117ce565b945050604086013567ffffffffffffffff811115611c7657611c75611780565b5b611c8288828901611b37565b935050606086013567ffffffffffffffff811115611ca357611ca2611780565b5b611caf88828901611b37565b925050608086013567ffffffffffffffff811115611cd057611ccf611780565b5b611cdc88828901611bec565b9150509295509295909350565b600067ffffffffffffffff821115611d0457611d03611a22565b5b602082029050602081019050919050565b6000611d28611d2384611ce9565b611a82565b90508083825260208201905060208402830185811115611d4b57611d4a611ac9565b5b835b81811015611d745780611d6088826117ce565b845260208401935050602081019050611d4d565b5050509392505050565b600082601f830112611d9357611d92611a1d565b5b8135611da3848260208601611d15565b91505092915050565b60008060408385031215611dc357611dc261177b565b5b600083013567ffffffffffffffff811115611de157611de0611780565b5b611ded85828601611d7e565b925050602083013567ffffffffffffffff811115611e0e57611e0d611780565b5b611e1a85828601611b37565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b611e59816117e3565b82525050565b6000611e6b8383611e50565b60208301905092915050565b6000602082019050919050565b6000611e8f82611e24565b611e998185611e2f565b9350611ea483611e40565b8060005b83811015611ed5578151611ebc8882611e5f565b9750611ec783611e77565b925050600181019050611ea8565b5085935050505092915050565b60006020820190508181036000830152611efc8184611e84565b905092915050565b611f0d816117a5565b82525050565b6000602082019050611f286000830184611f04565b92915050565b611f3781611908565b8114611f4257600080fd5b50565b600081359050611f5481611f2e565b92915050565b60008060408385031215611f7157611f7061177b565b5b6000611f7f858286016117ce565b9250506020611f9085828601611f45565b9150509250929050565b60008060408385031215611fb157611fb061177b565b5b6000611fbf858286016117ce565b9250506020611fd0858286016117ce565b9150509250929050565b600080600080600060a08688031215611ff657611ff561177b565b5b6000612004888289016117ce565b9550506020612015888289016117ce565b945050604061202688828901611804565b935050606061203788828901611804565b925050608086013567ffffffffffffffff81111561205857612057611780565b5b61206488828901611bec565b9150509295509295909350565b6000602082840312156120875761208661177b565b5b6000612095848285016117ce565b91505092915050565b7f455243313135353a2061646472657373207a65726f206973206e6f742061207660008201527f616c6964206f776e657200000000000000000000000000000000000000000000602082015250565b60006120fa602a83611976565b91506121058261209e565b604082019050919050565b60006020820190508181036000830152612129816120ed565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061217757607f821691505b60208210810361218a57612189612130565b5b50919050565b7f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60008201527f6572206f7220617070726f766564000000000000000000000000000000000000602082015250565b60006121ec602e83611976565b91506121f782612190565b604082019050919050565b6000602082019050818103600083015261221b816121df565b9050919050565b7f455243313135353a206163636f756e747320616e6420696473206c656e67746860008201527f206d69736d617463680000000000000000000000000000000000000000000000602082015250565b600061227e602983611976565b915061228982612222565b604082019050919050565b600060208201905081810360008301526122ad81612271565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061231d826117e3565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361234f5761234e6122e3565b5b600182019050919050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b60006123b6602683611976565b91506123c18261235a565b604082019050919050565b600060208201905081810360008301526123e5816123a9565b9050919050565b7f455243313135353a2069647320616e6420616d6f756e7473206c656e6774682060008201527f6d69736d61746368000000000000000000000000000000000000000000000000602082015250565b6000612448602883611976565b9150612453826123ec565b604082019050919050565b600060208201905081810360008301526124778161243b565b9050919050565b7f455243313135353a207472616e7366657220746f20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b60006124da602583611976565b91506124e58261247e565b604082019050919050565b60006020820190508181036000830152612509816124cd565b9050919050565b7f455243313135353a20696e73756666696369656e742062616c616e636520666f60008201527f72207472616e7366657200000000000000000000000000000000000000000000602082015250565b600061256c602a83611976565b915061257782612510565b604082019050919050565b6000602082019050818103600083015261259b8161255f565b9050919050565b60006125ad826117e3565b91506125b8836117e3565b92508282019050808211156125d0576125cf6122e3565b5b92915050565b600060408201905081810360008301526125f08185611e84565b905081810360208301526126048184611e84565b90509392505050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b6000612643602083611976565b915061264e8261260d565b602082019050919050565b6000602082019050818103600083015261267281612636565b9050919050565b7f455243313135353a206d696e7420746f20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b60006126d5602183611976565b91506126e082612679565b604082019050919050565b60006020820190508181036000830152612704816126c8565b9050919050565b60006040820190506127206000830185611859565b61272d6020830184611859565b9392505050565b7f455243313135353a2073657474696e6720617070726f76616c2073746174757360008201527f20666f722073656c660000000000000000000000000000000000000000000000602082015250565b6000612790602983611976565b915061279b82612734565b604082019050919050565b600060208201905081810360008301526127bf81612783565b9050919050565b600081519050919050565b600082825260208201905092915050565b60006127ed826127c6565b6127f781856127d1565b9350612807818560208601611987565b612810816119b1565b840191505092915050565b600060a0820190506128306000830188611f04565b61283d6020830187611f04565b818103604083015261284f8186611e84565b905081810360608301526128638185611e84565b9050818103608083015261287781846127e2565b90509695505050505050565b600081519050612892816118af565b92915050565b6000602082840312156128ae576128ad61177b565b5b60006128bc84828501612883565b91505092915050565b60008160e01c9050919050565b600060033d11156128f15760046000803e6128ee6000516128c5565b90505b90565b600060443d1061298157612906611771565b60043d036004823e80513d602482011167ffffffffffffffff8211171561292e575050612981565b808201805167ffffffffffffffff81111561294c5750505050612981565b80602083010160043d038501811115612969575050505050612981565b61297882602001850186611a51565b82955050505050505b90565b7f455243313135353a207472616e7366657220746f206e6f6e2d4552433131353560008201527f526563656976657220696d706c656d656e746572000000000000000000000000602082015250565b60006129e0603483611976565b91506129eb82612984565b604082019050919050565b60006020820190508181036000830152612a0f816129d3565b9050919050565b7f455243313135353a204552433131353552656365697665722072656a6563746560008201527f6420746f6b656e73000000000000000000000000000000000000000000000000602082015250565b6000612a72602883611976565b9150612a7d82612a16565b604082019050919050565b60006020820190508181036000830152612aa181612a65565b9050919050565b600060a082019050612abd6000830188611f04565b612aca6020830187611f04565b612ad76040830186611859565b612ae46060830185611859565b8181036080830152612af681846127e2565b9050969550505050505056fea2646970667358221220dc057c984306b0b6074ec7c11aa65ff07518e65613ff877b2ab44569f54ed28864736f6c63430008110033 \ No newline at end of file diff --git a/contracts/deposit/NFT/staking.sol b/contracts/deposit/NFT/staking.sol new file mode 100644 index 0000000..0cb73a1 --- /dev/null +++ b/contracts/deposit/NFT/staking.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "./IDeposit.sol"; +import "hardhat/console.sol"; + +contract NFT is ERC1155, Ownable { + constructor() ERC1155("") Ownable() {} + + function mint(address to, uint256 id) external onlyOwner { + //console.log(id); + _mint(to, id, 1, ""); + } +} + +contract Staking is IDeposit, IERC1155Receiver, Initializable { + uint256 constant T50 = uint256(keccak256("50AMT")); + uint256 constant T100 = uint256(keccak256("100AMT")); + uint256 constant T500 = uint256(keccak256("500AMT")); + + IERC1155 token; + mapping(address => uint256) depoistNFT; + mapping(address => uint256) depositExpire; + + constructor() {} + + function supportsInterface( + bytes4 interfaceId + ) external pure returns (bool) { + return + interfaceId == type(IDeposit).interfaceId || + interfaceId == type(IERC1155Receiver).interfaceId; + } + + function initialize(address addr) public initializer { + token = IERC1155(addr); + } + + function deposit( + bytes calldata pubkey, + bytes calldata signature, + uint256 tokenID + ) external { + require(pubkey.length == 48, "Staking: invalid public key"); + require(signature.length == 96, "Staking: invalid signature"); + require( + tokenID == T50 || tokenID == T100 || tokenID == T500, + "Staking: not allowed token" + ); + require(depoistNFT[msg.sender] == 0, "Staking: have deposited"); + require( + token.balanceOf(msg.sender, tokenID) > 0, + "Staking: Insuficient Allowance" + ); + require( + token.isApprovedForAll(msg.sender, address(this)), + "Staking: caller is not approved" + ); + + token.safeTransferFrom(msg.sender, address(this), tokenID, 1, ""); + + depoistNFT[msg.sender] = tokenID; + depositExpire[msg.sender] = block.timestamp + 365 days; + + emit DepositEvent(pubkey, nftToAmount(tokenID), signature); + } + + function withdraw() external { + require(depoistNFT[msg.sender] > 0, "Staking: not deposited"); + require( + depositExpire[msg.sender] <= block.timestamp, + "Staking: token is locked" + ); + + uint256 id = depoistNFT[msg.sender]; + delete depoistNFT[msg.sender]; + delete depositExpire[msg.sender]; + + token.safeTransferFrom(address(this), msg.sender, id, 1, ""); + emit WithdrawnEvent(nftToAmount(id)); + } + + function depositsOf(address account) external view returns (uint256) { + return depoistNFT[account]; + } + + function depositUnlockingTimestamp( + address account + ) external view returns (uint256) { + return depositExpire[account]; + } + + function disperseAMT( + address payable[] calldata recipients, + uint256[] calldata amounts + ) external payable { + require( + recipients.length > 0 && recipients.length == amounts.length, + "the lenght of receipts not equal amounts or length is 0 " + ); + + uint256 totalAmount; + for (uint i = 0; i < amounts.length; ) { + require(amounts[i] <= 0.7083 ether, "beyond the max rewards"); + unchecked { + totalAmount += amounts[i]; + ++i; + } + } + require(msg.value >= totalAmount, "Insufficient AMT sent"); + for (uint i = 0; i < recipients.length; i++) { + recipients[i].transfer(amounts[i]); + } + } + + function nftToAmount(uint256 id) internal pure returns (uint256) { + if (id == T50) { + return 50 ether; + } + + if (id == T100) { + return 100 ether; + } + + if (id == T500) { + return 500 ether; + } + + return 0; + } + + function onERC1155Received( + address /*operator*/, + address /*from*/, + uint256 /*id*/, + uint256 /*value*/, + bytes calldata /*data*/ + ) external pure returns (bytes4) { + return + bytes4( + keccak256( + "onERC1155Received(address,address,uint256,uint256,bytes)" + ) + ); + } + + function onERC1155BatchReceived( + address /*operator*/, + address /*from*/, + uint256[] calldata /*ids*/, + uint256[] calldata /*values*/, + bytes calldata /*data*/ + ) external returns (bytes4) {} +} \ No newline at end of file diff --git a/contracts/deposit/contract.go b/contracts/deposit/contract.go new file mode 100644 index 0000000..9a8f84c --- /dev/null +++ b/contracts/deposit/contract.go @@ -0,0 +1,317 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package deposit + +import ( + "context" + "github.com/astranetworld/ast/common" + "github.com/astranetworld/ast/common/crypto/bls" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/common/transaction" + "github.com/astranetworld/ast/common/types" + "github.com/astranetworld/ast/log" + event "github.com/astranetworld/ast/modules/event/v2" + "github.com/astranetworld/ast/modules/rawdb" + "github.com/astranetworld/ast/params" + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv" + "sync" +) + +const ( + // + DayPerMonth = 30 + // + fiftyDeposit = 50 + OneHundredDeposit = 100 + FiveHundredDeposit = 500 + + fuji2000Deposit = 2000 + fuji800Deposit = 800 + fuji200Deposit = 200 + // + fiftyDepositMaxTaskPerEpoch = 500 + OneHundredDepositMaxTaskPerEpoch = 100 + FiveHundredDepositMaxTaskPerEpoch = 100 + // + fujiMaxTaskPerEpoch = 50 + // + fiftyDepositRewardPerMonth = 0.375 * params.AMT + OneHundredDepositRewardPerMonth = 1 * params.AMT + FiveHundredDepositRewardPerMonth = 6.25 * params.AMT //max uint64 = ^uint64(0) ≈ 18.44 AMT so 15 AMT is ok + // + fuji200RewardPerEpoch = 0.025 * params.AMT + fuji800RewardPerEpoch = 0.1 * params.AMT + fuji2000RewardPerMonth = 10 * params.AMT +) + +// DepositContract d +type DepositContract interface { + WithdrawnSignature() types.Hash + DepositSignature() types.Hash + UnpackDepositLogData(data []byte) (publicKey []byte, signature []byte, depositAmount *uint256.Int, err error) + IsDepositAction(sig [4]byte) bool +} + +func GetDepositInfo(tx kv.Tx, addr types.Address) *Info { + + pubkey, depositAmount, err := rawdb.GetDeposit(tx, addr) + if err != nil { + return nil + } + + var ( + maxRewardPerEpoch *uint256.Int + rewardPerBlock *uint256.Int + ) + depositEther := new(uint256.Int).Div(depositAmount, uint256.NewInt(params.AMT)).Uint64() + switch depositEther { + case fiftyDeposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fiftyDepositRewardPerMonth), uint256.NewInt(DayPerMonth*fiftyDepositMaxTaskPerEpoch)) + maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(fiftyDepositMaxTaskPerEpoch)) + case OneHundredDeposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(OneHundredDepositRewardPerMonth), uint256.NewInt(DayPerMonth*OneHundredDepositMaxTaskPerEpoch)) + rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei)) + maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(OneHundredDepositMaxTaskPerEpoch)) + case FiveHundredDeposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(FiveHundredDepositRewardPerMonth), uint256.NewInt(DayPerMonth*FiveHundredDepositMaxTaskPerEpoch)) + rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei)) + // + maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(FiveHundredDepositMaxTaskPerEpoch)) + case fuji200Deposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji200RewardPerEpoch), uint256.NewInt(fujiMaxTaskPerEpoch)) + maxRewardPerEpoch = new(uint256.Int).SetUint64(fuji200RewardPerEpoch) + case fuji800Deposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji800RewardPerEpoch), uint256.NewInt(fujiMaxTaskPerEpoch)) + maxRewardPerEpoch = new(uint256.Int).SetUint64(fuji800RewardPerEpoch) + case fuji2000Deposit: + rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji2000RewardPerMonth), uint256.NewInt(DayPerMonth*fujiMaxTaskPerEpoch)) + rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei)) + + maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(fujiMaxTaskPerEpoch)) + case 10: //todo + return nil + default: + panic("wrong deposit amount") + } + + return &Info{ + pubkey, + depositAmount, + rewardPerBlock, + maxRewardPerEpoch, + } +} + +type Info struct { + PublicKey types.PublicKey `json:"PublicKey"` + DepositAmount *uint256.Int `json:"DepositAmount"` + RewardPerBlock *uint256.Int `json:"RewardPerBlock"` + MaxRewardPerEpoch *uint256.Int `json:"MaxRewardPerEpoch"` +} + +//func NewInfo(depositAmount uint256.Int, publicKey bls.PublicKey) *Info { +// return &Info{ +// PublicKey: publicKey, +// DepositAmount: depositAmount, +// } +//} + +type Deposit struct { + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + blockChain common.IBlockChain + db kv.RwDB + + logsSub event.Subscription // Subscription for new log event + rmLogsSub event.Subscription // Subscription for removed log event + + logsCh chan common.NewLogsEvent // Channel to receive new log event + rmLogsCh chan common.RemovedLogsEvent // Channel to receive removed log event + + depositContracts map[types.Address]DepositContract +} + +func NewDeposit(ctx context.Context, bc common.IBlockChain, db kv.RwDB, depositContracts map[types.Address]DepositContract) *Deposit { + c, cancel := context.WithCancel(ctx) + d := &Deposit{ + ctx: c, + cancel: cancel, + blockChain: bc, + db: db, + logsCh: make(chan common.NewLogsEvent), + rmLogsCh: make(chan common.RemovedLogsEvent), + depositContracts: depositContracts, + } + + d.logsSub = event.GlobalEvent.Subscribe(d.logsCh) + d.rmLogsSub = event.GlobalEvent.Subscribe(d.rmLogsCh) + + if d.logsSub == nil || d.rmLogsSub == nil { + log.Error("Subscribe for event system failed") + } + return d +} + +func (d *Deposit) Start() { + d.wg.Add(1) + go d.eventLoop() +} + +func (d *Deposit) Stop() error { + d.cancel() + d.wg.Wait() + return nil +} + +func (d *Deposit) IsDepositAction(txs *transaction.Transaction) bool { + var ( + depositContract DepositContract + foundDepositContract bool + ) + to := txs.To() + if to == nil { + return false + } + if depositContract, foundDepositContract = d.depositContracts[*to]; !foundDepositContract { + return false + } + + if len(txs.Data()) < 4 { + return false + } + + var sig [4]byte + copy(sig[:], txs.Data()[:4]) + if !depositContract.IsDepositAction(sig) { + return false + } + + return true +} + +func (d *Deposit) eventLoop() { + // Ensure all subscriptions get cleaned up + defer func() { + d.logsSub.Unsubscribe() + d.rmLogsSub.Unsubscribe() + d.wg.Done() + log.Info("Context closed, exiting goroutine (eventLoop)") + }() + + for { + select { + case logEvent := <-d.logsCh: + for _, l := range logEvent.Logs { + if depositContract, found := d.depositContracts[l.Address]; found { + if l.Topics[0] == depositContract.DepositSignature() { + d.handleDepositEvent(l.TxHash, l.Data, depositContract) + } else if l.Topics[0] == depositContract.WithdrawnSignature() { + d.handleWithdrawnEvent(l.TxHash, l.Data) + } + } + } + case logRemovedEvent := <-d.rmLogsCh: + for _, l := range logRemovedEvent.Logs { + log.Info("logEvent", "address", l.Address, "data", l.Data, "") + } + case <-d.logsSub.Err(): + return + case <-d.rmLogsSub.Err(): + return + case <-d.ctx.Done(): + return + } + } +} + +func (d *Deposit) handleDepositEvent(txHash types.Hash, data []byte, depositContract DepositContract) { + // 1 + pb, sig, amount, err := depositContract.UnpackDepositLogData(data) + if err != nil { + log.Warn("cannot unpack deposit log data", "err", err) + return + } + // 2 + signature, err := bls.SignatureFromBytes(sig) + if err != nil { + log.Warn("cannot unpack BLS signature", "signature", hexutil.Encode(sig), "err", err) + return + } + // 3 + publicKey, err := bls.PublicKeyFromBytes(pb) + if err != nil { + log.Warn("cannot unpack BLS publicKey", "publicKey", hexutil.Encode(pb), "err", err) + return + } + // 4 + log.Trace("DepositEvent verify:", "signature", hexutil.Encode(signature.Marshal()), "publicKey", hexutil.Encode(publicKey.Marshal()), "msg", hexutil.Encode(amount.Bytes())) + if signature.Verify(publicKey, amount.Bytes()) { + var tx *transaction.Transaction + rwTx, err := d.db.BeginRw(d.ctx) + defer rwTx.Rollback() + if err != nil { + log.Error("cannot open db", "err", err) + return + } + + tx, _, _, _, err = rawdb.ReadTransactionByHash(rwTx, txHash) + if err != nil { + log.Error("rawdb.ReadTransactionByHash", "err", err, "hash", txHash) + } + + if tx != nil { + log.Info("add Deposit info", "address", tx.From(), "amount", amount.String()) + + var pub types.PublicKey + pub.SetBytes(publicKey.Marshal()) + // + rawdb.PutDeposit(rwTx, *tx.From(), pub, *amount) + rwTx.Commit() + } + } else { + log.Error("DepositEvent cannot Verify signature", "signature", hexutil.Encode(sig), "publicKey", hexutil.Encode(pb), "message", hexutil.Encode(amount.Bytes()), "err", err) + } +} + +func (d *Deposit) handleWithdrawnEvent(txHash types.Hash, data []byte) { + var tx *transaction.Transaction + + rwTx, err := d.db.BeginRw(d.ctx) + defer rwTx.Rollback() + if err != nil { + log.Error("cannot open db", "err", err) + return + } + tx, _, _, _, err = rawdb.ReadTransactionByHash(rwTx, txHash) + if err != nil { + log.Error("rawdb.ReadTransactionByHash", "err", err, "hash", txHash) + return + } + if tx == nil { + log.Error("cannot find Transaction", "err", err, "hash", txHash) + return + } + + err = rawdb.DeleteDeposit(rwTx, *tx.From()) + if err != nil { + log.Error("cannot delete deposit", "err", err) + return + } + rwTx.Commit() +} diff --git a/contracts/deposit/contract_test.go b/contracts/deposit/contract_test.go new file mode 100644 index 0000000..8a88ef6 --- /dev/null +++ b/contracts/deposit/contract_test.go @@ -0,0 +1,79 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package deposit + +import ( + "github.com/astranetworld/ast/common/crypto/bls" + "github.com/astranetworld/ast/common/hexutil" + "github.com/astranetworld/ast/params" + "github.com/holiman/uint256" + "testing" +) + +func TestBLS(t *testing.T) { + sig, _ := hexutil.Decode("0xab22c6b63e3595630ffe8ed2903dfeba2a781c2d33dc66f88442982b65c5fcce9a8078f9ae419c95eecd2a5546a06e371196855311a930a4ad404321083f4f058c41e2d6c2e1f2bf11b1cd9b73d65a0a169a81cc1e60b50164aa7b322396be67") + bp, _ := hexutil.Decode("0xa20699fa55487f79c1400e2be5bb6acf89b0c5880becfa4b0560b9994bd8050616886a8fb71bdf15065dd31dd2858c18") + msg := new(uint256.Int).Mul(uint256.NewInt(params.AMT), uint256.NewInt(50)) //50AMT + signature, err := bls.SignatureFromBytes(sig) + if err != nil { + t.Fatal("cannot unpack BLS signature", err) + } + + publicKey, err := bls.PublicKeyFromBytes(bp) + + if err != nil { + t.Fatal("cannot unpack BLS publicKey", err) + } + + if !signature.Verify(publicKey, msg.Bytes()) { + t.Fatal("bls cannot verify signature") + } + +} + +func TestUint256(t *testing.T) { + amt50Hex, _ := hexutil.Decode("0x2B5E3AF16B1880000") // 50 AMT + amt50Uint256 := new(uint256.Int).Mul(uint256.NewInt(params.AMT), uint256.NewInt(50)) + // + t.Logf("50 AMT uint256 bytes:%s, hex Bytes: %s", hexutil.Encode(amt50Uint256.Bytes()), hexutil.Encode(amt50Hex)) + + amt500Hex, _ := hexutil.Decode("0x1B1AE4D6E2EF500000") // 500 AMT + amt500Uint256 := new(uint256.Int).Mul(uint256.NewInt(params.AMT), uint256.NewInt(500)) + // + t.Logf("500 AMT uint256 bytes:%s, hex Bytes: %s", hexutil.Encode(amt500Uint256.Bytes()), hexutil.Encode(amt500Hex)) + + amt100Hex, _ := hexutil.Decode("0x56BC75E2D63100000") // 100 AMT + amt100Uint256 := new(uint256.Int).Mul(uint256.NewInt(params.AMT), uint256.NewInt(100)) + // + t.Logf("100 AMT uint256 bytes:%s, hex Bytes: %s", hexutil.Encode(amt100Uint256.Bytes()), hexutil.Encode(amt100Hex)) +} + +//func TestPrivateKey(t *testing.T) { +// private, _ := hexutil.Decode("0xde4b76c3dca3d8e10aea7644f77b316a68a6476fbd119d441ead5c6131aa42a7") +// +// p, err := bls.SecretKeyFromBytes(private) +// if err != nil { +// t.Fatal("bls cannot import private key", err) +// } +// +// pub, err := p.PublicKey().MarshalText() +// +// if err != nil { +// t.Fatal("bls public key cannot MarshalText ", err) +// } +// t.Logf("pubkey %s", string(pub)) +//} diff --git a/core/error.go b/core/error.go new file mode 100644 index 0000000..d63332a --- /dev/null +++ b/core/error.go @@ -0,0 +1,101 @@ +// Copyright 2023 The astranet Authors +// This file is part of the astranet library. +// +// The astranet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The astranet library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the astranet library. If not, see . + +package core + +import ( + "errors" + "fmt" +) + +var ( + ErrInvalidBlock = fmt.Errorf("invalid block") + ErrInvalidPubSub = fmt.Errorf("PubSub is nil") +) + +var ( + + // ErrBannedHash is returned if a block to import is on the banned list. + ErrBannedHash = errors.New("banned hash") + + // ErrNoGenesis is returned when there is no Genesis Block. + ErrNoGenesis = errors.New("genesis not found in chain") + + errSideChainReceipts = errors.New("side blocks can't be accepted as ancient chain data") +) + +// List of evm-call-message pre-checking errors. All state transition messages will +// be pre-checked before execution. If any invalidation detected, the corresponding +// error should be returned which is defined here. +// +// - If the pre-checking happens in the miner, then the transaction won't be packed. +// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk" +// error should be emitted. +var ( + // ErrNonceTooLow is returned if the nonce of a transaction is lower than the + // one present in the local chain. + ErrNonceTooLow = errors.New("nonce too low") + + // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the + // next one expected based on the local chain. + ErrNonceTooHigh = errors.New("nonce too high") + + // ErrNonceMax is returned if the nonce of a transaction sender account has + // maximum allowed value and would become invalid if incremented. + ErrNonceMax = errors.New("nonce has max value") + + // ErrGasLimitReached is returned by the gas pool if the amount of gas required + // by a transaction is higher than what's left in the block. + ErrGasLimitReached = errors.New("gas limit reached") + + // ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't + // have enough funds for transfer(topmost call only). + ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") + + // ErrInsufficientFunds is returned if the total cost of executing a transaction + // is higher than the balance of the user's account. + ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") + + // ErrGasUintOverflow is returned when calculating gas usage. + ErrGasUintOverflow = errors.New("gas uint64 overflow") + + // ErrIntrinsicGas is returned if the transaction is specified to use less gas + // than required to start the invocation. + ErrIntrinsicGas = errors.New("intrinsic gas too low") + + // ErrTxTypeNotSupported is returned if a transaction is not supported in the + // current network configuration. + ErrTxTypeNotSupported = errors.New("transaction type not supported") + + // ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a + // transaction with a tip higher than the total fee cap. + ErrTipAboveFeeCap = errors.New("max priority fee per gas higher than max fee per gas") + + // ErrTipVeryHigh is a sanity error to avoid extremely big numbers specified + // in the tip field. + ErrTipVeryHigh = errors.New("max priority fee per gas higher than 2^256-1") + + // ErrFeeCapVeryHigh is a sanity error to avoid extremely big numbers specified + // in the fee cap field. + ErrFeeCapVeryHigh = errors.New("max fee per gas higher than 2^256-1") + + // ErrFeeCapTooLow is returned if the transaction fee cap is less than the + // the base fee of the block. + ErrFeeCapTooLow = errors.New("max fee per gas less than block base fee") + + // ErrSenderNoEOA is returned if the sender of a transaction is a contract. + ErrSenderNoEOA = errors.New("sender not an eoa") +) diff --git a/deployments/explorer/config.env b/deployments/explorer/config.env new file mode 100644 index 0000000..00b7ec1 --- /dev/null +++ b/deployments/explorer/config.env @@ -0,0 +1,13 @@ +NETWORK="ast astranet" +SUBNETWORK=astranet Explorer +ETHEREUM_JSONRPC_VARIANT=geth +BLOCK_TRANSFORMER=clique +ETHEREUM_JSONRPC_HTTP_URL=http://bootnode:20012 +ETHEREUM_JSONRPC_WS_URL=ws://bootnode:20013 +ECTO_USE_SSL=false +COIN=SNX +BLOCKSCOUT_VERSION=1.0 +COIN_NAME=ast +LOGO=/images/blockscout_logo_ast.svg +# INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=true +# INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER=true \ No newline at end of file diff --git a/deployments/influxdb/config.yml b/deployments/influxdb/config.yml new file mode 100644 index 0000000..abd60f3 --- /dev/null +++ b/deployments/influxdb/config.yml @@ -0,0 +1,70 @@ +assets-path: "" +bolt-path: /var/lib/influxdb2/influxd.bolt +e2e-testing: false +engine-path: /var/lib/influxdb2/engine +feature-flags: {} +flux-log-enabled: false +hardening-enabled: false +http-bind-address: :8086 +http-idle-timeout: 3m0s +http-read-header-timeout: 10s +http-read-timeout: 0s +http-write-timeout: 0s +influxql-max-select-buckets: 0 +influxql-max-select-point: 0 +influxql-max-select-series: 0 +instance-id: "" +key-name: "" +log-level: info +metrics-disabled: false +nats-max-payload-bytes: 0 +nats-port: 4222 +no-tasks: false +pprof-disabled: false +query-concurrency: 1024 +query-initial-memory-bytes: 0 +query-max-memory-bytes: 0 +query-memory-bytes: 0 +query-queue-size: 1024 +reporting-disabled: false +secret-store: bolt +session-length: 60 +session-renew-disabled: false +sqlite-path: "" +storage-cache-max-memory-size: 1073741824 +storage-cache-snapshot-memory-size: 26214400 +storage-cache-snapshot-write-cold-duration: 10m0s +storage-compact-full-write-cold-duration: 4h0m0s +storage-compact-throughput-burst: 50331648 +storage-max-concurrent-compactions: 0 +storage-max-index-log-file-size: 1048576 +storage-no-validate-field-size: false +storage-retention-check-interval: 30m0s +storage-series-file-max-concurrent-snapshot-compactions: 0 +storage-series-id-set-cache-size: 0 +storage-shard-precreator-advance-period: 30m0s +storage-shard-precreator-check-interval: 10m0s +storage-tsm-use-madv-willneed: false +storage-validate-keys: false +storage-wal-fsync-delay: 0s +storage-wal-max-concurrent-writes: 0 +storage-wal-max-write-delay: 10m0s +storage-write-timeout: 10s +store: disk +testing-always-allow-setup: false +tls-cert: "" +tls-key: "" +tls-min-version: "1.2" +tls-strict-ciphers: false +tracing-type: "" +ui-disabled: false +vault-addr: "" +vault-cacert: "" +vault-capath: "" +vault-client-cert: "" +vault-client-key: "" +vault-client-timeout: 0s +vault-max-retries: 0 +vault-skip-verify: false +vault-tls-server-name: "" +vault-token: "" diff --git a/deployments/influxdb/influx-configs b/deployments/influxdb/influx-configs new file mode 100644 index 0000000..f5c64b6 --- /dev/null +++ b/deployments/influxdb/influx-configs @@ -0,0 +1,5 @@ +[default] + url = "http://influxdb:8086" + token = "ksLlfJ0cRCNXRtrFVpKiZ2au3-pOWL-hgVhgsCqivAlq7Vf0VdyjjVMWA__IhjkcI_GbWccpwRDpQiIGdm2FpA==" + org = "astranet" + active = true \ No newline at end of file diff --git a/deployments/prometheus/dashboards/amc.json b/deployments/prometheus/dashboards/amc.json new file mode 100644 index 0000000..3443277 --- /dev/null +++ b/deployments/prometheus/dashboards/amc.json @@ -0,0 +1,1911 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 4, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Blockchain", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 5, + "x": 0, + "y": 1 + }, + "id": 110, + "links": [], + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "sync{instance=~\"$instance\",stage=\"headers\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "header: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "chain_head_block{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "block: {{instance}}", + "refId": "C" + } + ], + "title": "Chain head", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 5, + "x": 5, + "y": 1 + }, + "id": 116, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "txpool_pending{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "pending: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "txpool_local{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "local: {{instance}}", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "txpool_queued{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "queued: {{instance}}", + "range": true, + "refId": "B" + } + ], + "title": "Transaction pool", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 7, + "x": 10, + "y": 1 + }, + "id": 106, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "increase(process_cpu_seconds_system_total{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "system: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "increase(process_cpu_seconds_user_total{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "iowait: {{instance}}", + "refId": "B" + } + ], + "title": "CPU", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 7, + "x": 17, + "y": 1 + }, + "id": 154, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_stack_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "stack_sys: {{ instance }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "max: {{ instance }}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_stack_inuse_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "stack_inuse: {{ instance }}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_mspan_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "mspan_sys: {{ instance }}", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_mcache_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "mcache_sys: {{ instance }}", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_heap_alloc_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "current: {{ instance }}", + "range": true, + "refId": "F" + } + ], + "title": "Memory Use", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "sign number", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 12 + }, + "id": 197, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "block_sign_counter{instance=~\"$instance\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "sign:{{instance}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Block signatures", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 7, + "x": 10, + "y": 12 + }, + "id": 77, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "p2p_peer_count{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "peers: {{instance}}-{{state}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "rate(p2p_dials{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "dials: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "rate(p2p_serves{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "serves: {{instance}}", + "refId": "C" + } + ], + "title": "Peers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 7, + "x": 17, + "y": 12 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(p2p_ingress{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "ingress: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(p2p_egress{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "egress: {{instance}}", + "refId": "C" + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 10, + "y": 23 + }, + "id": 85, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_storage_read_bytes_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "read: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_storage_written_bytes_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "write: {{instance}}", + "refId": "B" + } + ], + "title": "Disk bytes/sec", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 17, + "y": 23 + }, + "id": 159, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "db_size{instance=~\"$instance\"}", + "interval": "", + "legendFormat": "size: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "db_mi_last_pgno{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "db_mi_last_pgno: {{instance}}", + "range": true, + "refId": "B" + } + ], + "title": "DB Size", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 183, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "RPC", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 185, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(rpc_duration_seconds_count{instance=~\"$instance\",success=\"success\"}[1m])", + "interval": "", + "legendFormat": "success {{ method }} {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(rpc_duration_seconds_count{instance=~\"$instance\",success=\"failure\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "failure {{ method }} {{ instance }} ", + "refId": "B" + } + ], + "title": "RPS", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 187, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rpc_duration_seconds{quantile=\"$quantile\",instance=~\"$instance\"}", + "interval": "", + "legendFormat": " {{ method }} {{ instance }} {{ success }}", + "refId": "A" + } + ], + "title": "Timings", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 12, + "y": 40 + }, + "id": 189, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_keys_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "keys: {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_list_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "list: {{ instance }} ", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_code_keys_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "code_keys: {{ instance }} ", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_code_list_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "code_list: {{ instance }} ", + "refId": "D" + } + ], + "title": "Cache keys", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 19, + "y": 40 + }, + "id": 184, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(delta(cache_total{result=\"hit\",name=\"rpc\",instance=~\"$instance\"}[1m]))/sum(delta(cache_total{name=\"rpc\",instance=~\"$instance\"}[1m])) ", + "hide": false, + "interval": "", + "legendFormat": "hit rate: {{ instance }} ", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "sum(delta(cache_code_total{result=\"hit\",name=\"rpc\",instance=~\"$instance\"}[1m]))/sum(delta(cache_code_total{name=\"rpc\",instance=~\"$instance\"}[1m])) ", + "hide": false, + "interval": "", + "legendFormat": "code hit rate: {{ instance }} ", + "refId": "B" + } + ], + "title": "Cache hit-rate", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 48 + }, + "id": 138, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Private api", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 49 + }, + "hiddenSeries": false, + "id": 136, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "sum by (grpc_service, grpc_method, instance) (rate(grpc_server_started_total{instance=~\"$instance\"}[1m]))", + "interval": "", + "legendFormat": "Calls: {{grpc_service}}.{{grpc_method}}, {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "sum by (grpc_service, grpc_method, instance) (rate(grpc_server_handled_total{instance=~\"$instance\",grpc_code!=\"OK\"}[1m])) ", + "interval": "", + "legendFormat": "Errors: {{grpc_service}}.{{grpc_method}}, {{instance}}", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "gRPC call, error rates ", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "30s", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "0.97", + "value": "0.97" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "quantile", + "options": [ + { + "selected": false, + "text": "0.0", + "value": "0.0" + }, + { + "selected": false, + "text": "0.25", + "value": "0.25" + }, + { + "selected": false, + "text": "0.5", + "value": "0.5" + }, + { + "selected": false, + "text": "0.9", + "value": "0.9" + }, + { + "selected": true, + "text": "0.97", + "value": "0.97" + }, + { + "selected": false, + "text": "0.99", + "value": "0.99" + }, + { + "selected": false, + "text": "1", + "value": "1" + } + ], + "query": "0.0,0.25,0.5, 0.9, 0.97, 0.99, 1", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "go_goroutines", + "hide": 0, + "includeAll": true, + "label": "instance", + "multi": true, + "name": "instance", + "options": [], + "query": { + "query": "go_goroutines", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/.*instance=\"([^\"]*).*/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "auto": false, + "auto_count": 30, + "auto_min": "10s", + "current": { + "selected": false, + "text": "10m", + "value": "10m" + }, + "hide": 0, + "label": "Rate Interval", + "name": "rate_interval", + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "3h", + "value": "3h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,3h,6h,12h,1d,7d,14d,30d", + "queryValue": "", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "astranet", + "uid": "FPpjH6Hik", + "version": 14, + "weekStart": "" +} \ No newline at end of file diff --git a/deployments/prometheus/dashboards/amc_internal.json b/deployments/prometheus/dashboards/amc_internal.json new file mode 100644 index 0000000..c63b59d --- /dev/null +++ b/deployments/prometheus/dashboards/amc_internal.json @@ -0,0 +1,4600 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 171, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Blocks execution", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "0.025" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 199, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": " ceil(increase(chain_execution_bucket{instance=~\"$instance\", le!=\"+Inf\"}[1m]))", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Block Execution speed ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "0.025" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 202, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": " ceil(increase(chain_inserts_bucket{instance=~\"$instance\", le!=\"+Inf\"}[1m]))", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Block insert speed ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "0.025" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 201, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": " ceil(increase(chain_validation_bucket{instance=~\"$instance\", le!=\"+Inf\"}[1m]))", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Block validate speed ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "0.025" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 6 + }, + "id": 200, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": " ceil(increase(chain_write_bucket{instance=~\"$instance\", le!=\"+Inf\"}[1m]))", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Block write speed ", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 17, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Database", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0.001, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 141, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_commit_seconds_count{phase=\"total\",instance=~\"$instance\"}[$rate_interval])", + "interval": "", + "legendFormat": "commit: {{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Commit", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 2, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 16, + "x": 8, + "y": 12 + }, + "id": 166, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_commit_seconds{phase=\"total\",quantile=\"$quantile\",instance=~\"$instance\"}", + "interval": "", + "legendFormat": "total: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_commit_seconds{phase=\"gc_wall_clock\",quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_wall_clock: {{instance}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_commit_seconds{phase=\"write\",quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "write: {{instance}}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_commit_seconds{phase=\"sync\",quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "sync: {{instance}}", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_self_rtime_cpu{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_self_rtime_cpu: {{instance}}", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_work_rtime_cpu{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_work_rtime_cpu: {{instance}}", + "range": true, + "refId": "F" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_work_rtime{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_work_rtime: {{instance}}", + "range": true, + "refId": "G" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_self_rtime{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_self_rtime: {{instance}}", + "range": true, + "refId": "H" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_commit_seconds{phase=\"gc_cpu_time\",quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_cpu_time: {{instance}}", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_self_xtime{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_self_xtime: {{instance}}", + "range": true, + "refId": "J" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_work_pnl_merge_time{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "work_pnl_merge_time: {{instance}}", + "range": true, + "refId": "K" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_slef_pnl_merge_time{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "self_pnl_merge_time: {{instance}}", + "range": true, + "refId": "L" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "db_gc_work_xtime{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_work_xtime: {{instance}}", + "range": true, + "refId": "M" + } + ], + "title": "Commit speed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 159, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "db_size{instance=~\"$instance\"}", + "interval": "", + "legendFormat": "size: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "db_mi_last_pgno{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "db_mi_last_pgno: {{instance}}", + "range": true, + "refId": "B" + } + ], + "title": "DB Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 8, + "y": 21 + }, + "id": 168, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"newly\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "newly: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"cow\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "cow: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"clone\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "clone: {{instance}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"split\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "split: {{instance}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"merge\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "merge: {{instance}}", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"spill\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "spill: {{instance}}", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"wops\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "wops: {{instance}}", + "refId": "G" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"unspill\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "unspill: {{instance}}", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"gcrloops\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "gcrloops: {{instance}}", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"gcwloops\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "gcwloops: {{instance}}", + "range": true, + "refId": "J" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"gcxpages\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "gcxpages: {{instance}}", + "range": true, + "refId": "K" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"msync\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "msync: {{instance}}", + "range": true, + "refId": "L" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(db_pgops{phase=\"fsync\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "fsync: {{instance}}", + "range": true, + "refId": "M" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"minicore\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "minicore: {{instance}}", + "refId": "N" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(db_pgops{phase=\"prefault\", instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "prefault: {{instance}}", + "refId": "O" + } + ], + "title": "DB Pages Ops/sec", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 22 + }, + "id": 167, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "tx_limit{instance=~\"$instance\"}", + "interval": "", + "legendFormat": "limit: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "tx_dirty{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "dirty: {{instance}}", + "range": true, + "refId": "B" + } + ], + "title": "Tx Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "gc_overflow: mainnet2-1:6061" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 28 + }, + "id": 169, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "db_gc_leaf{instance=~\"$instance\"}", + "interval": "", + "legendFormat": "gc_leaf: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "db_gc_overflow{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "gc_overflow: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "exec_steps_in_db{instance=~\"$instance\"}/100", + "hide": false, + "interval": "", + "legendFormat": "exec_steps_in_db: {{instance}}", + "range": true, + "refId": "E" + } + ], + "title": "GC and State", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 28 + }, + "id": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_minor_pagefaults_total{instance=~\"$instance\"}[$rate_interval])", + "interval": "", + "legendFormat": "soft: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_major_pagefaults_total{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "hard: {{instance}}", + "refId": "B" + } + ], + "title": "getrusage: minflt - soft page faults (reclaims), majflt - hard faults", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 134, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Process", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 35 + }, + "id": 148, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "process_virtual_memory_bytes{instance=~\"$instance\"}", + "hide": true, + "interval": "", + "legendFormat": "resident virtual mem: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "process_resident_memory_anon_bytes{instance=~\"$instance\"}", + "hide": true, + "interval": "", + "legendFormat": "resident anon mem: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "process_resident_memory_bytes{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "resident mem: {{instance}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "mem_data{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "data: {{instance}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "mem_stack{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "stack: {{instance}}", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "mem_locked{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "locked: {{instance}}", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "mem_swap{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "swap: {{instance}}", + "refId": "G" + } + ], + "title": "mem: resident set size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 35 + }, + "id": 155, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_write_syscalls_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "in: {{instance}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_read_syscalls_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "out: {{instance}}", + "refId": "D" + } + ], + "title": "Read/Write syscall/sec", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "cps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 35 + }, + "id": 153, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(go_cgo_calls_count{instance=~\"$instance\"}[$rate_interval])", + "interval": "", + "legendFormat": "cgo_calls_count: {{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "cgo calls", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 40 + }, + "id": 86, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(go_memstats_mallocs_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "memstats_mallocs_total: {{ instance }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(go_memstats_frees_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "memstats_frees_total: {{ instance }}", + "range": true, + "refId": "B" + } + ], + "title": "Process Mem: allocate objects/sec, free", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 41 + }, + "id": 85, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_storage_read_bytes_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "read: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(process_io_storage_written_bytes_total{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "write: {{instance}}", + "refId": "B" + } + ], + "title": "Disk bytes/sec", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 41 + }, + "id": 128, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "go_goroutines{instance=~\"$instance\"}", + "instant": false, + "interval": "", + "legendFormat": "goroutines: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "expr": "go_threads{instance=~\"$instance\"}", + "instant": false, + "interval": "", + "legendFormat": "threads: {{instance}}", + "refId": "B" + } + ], + "title": "GO Goroutines and Threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 45 + }, + "id": 106, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "increase(process_cpu_seconds_system_total{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "system: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "increase(process_cpu_seconds_user_total{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "iowait: {{instance}}", + "refId": "B" + } + ], + "title": "CPU", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 47 + }, + "id": 154, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_stack_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "stack_sys: {{ instance }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "sys: {{ instance }}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_stack_inuse_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "stack_inuse: {{ instance }}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_mspan_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "mspan_sys: {{ instance }}", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_mcache_sys_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "mcache_sys: {{ instance }}", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "go_memstats_heap_alloc_bytes{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "heap_alloc: {{ instance }}", + "range": true, + "refId": "F" + } + ], + "title": "go memstat", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 47 + }, + "id": 124, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(go_gc_duration_seconds{quantile=\"0.75\",instance=~\"$instance\"}[$rate_interval])", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "GC Stop the World per sec", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 53 + }, + "id": 173, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "TxPool", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 54 + }, + "id": 175, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_process_remote_txs{quantile=\"$quantile\",instance=~\"$instance\"}", + "interval": "", + "legendFormat": "process_remote_txs: {{ instance }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_add_remote_txs{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "add_remote_txs: {{ instance }}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_new_block{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "new_block: {{ instance }}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_write_to_db{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "write_to_db: {{ instance }}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_propagate_to_new_peer{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "propagate_to_new_peer: {{ instance }}", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "pool_propagate_new_txs{quantile=\"$quantile\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "propagate_new_txs: {{ instance }}", + "refId": "F" + } + ], + "title": "Timings", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 54 + }, + "id": 177, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(pool_process_remote_txs_count{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "pool_process_remote_txs_count: {{ instance }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(pool_add_remote_txs_count{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "pool_add_remote_txs_count: {{ instance }}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(pool_new_block_count{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "pool_new_block_count: {{ instance }}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(pool_write_to_db_count{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "pool_write_to_db_count: {{ instance }}", + "refId": "D" + } + ], + "title": "RPS", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 62 + }, + "id": 176, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "sum(delta(cache_total{result=\"hit\",name=\"txpool\",instance=~\"$instance\"}[1m]))/sum(delta(cache_total{name=\"txpool\",instance=~\"$instance\"}[1m])) ", + "hide": false, + "interval": "", + "legendFormat": "hit rate: {{ instance }} ", + "refId": "A" + } + ], + "title": "Cache hit-rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 62 + }, + "id": 180, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(cache_total{name=\"txpool\",instance=~\"$instance\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "{{ result }}: {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(cache_timeout_total{name=\"txpool\",instance=~\"$instance\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "timeout: {{ instance }} ", + "refId": "B" + } + ], + "title": "Cache rps", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 62 + }, + "id": 181, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_keys_total{name=\"txpool\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "keys: {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_list_total{name=\"txpool\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "list: {{ instance }} ", + "refId": "B" + } + ], + "title": "Cache keys", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 68 + }, + "id": 178, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(pool_write_to_db_bytes{instance=~\"$instance\"}[$rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "pool_write_to_db_bytes: {{ instance }}", + "refId": "A" + } + ], + "title": "DB", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 74 + }, + "id": 183, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "RPC", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 75 + }, + "id": 185, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(rpc_duration_seconds_count{instance=~\"$instance\",success=\"success\"}[1m])", + "interval": "", + "legendFormat": "success {{ method }} {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(rpc_duration_seconds_count{instance=~\"$instance\",success=\"failure\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "failure {{ method }} {{ instance }} ", + "refId": "B" + } + ], + "title": "RPS", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 75 + }, + "id": 186, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "db_begin_seconds{quantile=\"$quantile\",instance=~\"$instance\"}", + "interval": "", + "legendFormat": "db_begin_seconds: {{ method }} {{ instance }}", + "refId": "A" + } + ], + "title": "DB begin", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 83 + }, + "id": 187, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rpc_duration_seconds{quantile=\"$quantile\",instance=~\"$instance\"}", + "interval": "", + "legendFormat": " {{ method }} {{ instance }} {{ success }}", + "refId": "A" + } + ], + "title": "Timings", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 83 + }, + "id": 188, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "go_goroutines{instance=~\"$instance\"}", + "instant": false, + "interval": "", + "legendFormat": "go/goroutines: {{instance}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "go_threads{instance=~\"$instance\"}", + "instant": false, + "interval": "", + "legendFormat": "go/threads: {{instance}}", + "refId": "B" + } + ], + "title": "GO Goroutines and Threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 91 + }, + "id": 189, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_keys_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "keys: {{ instance }} ", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_list_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "list: {{ instance }} ", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_code_keys_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "code_keys: {{ instance }} ", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "cache_code_list_total{name=\"rpc\",instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "code_list: {{ instance }} ", + "refId": "D" + } + ], + "title": "Cache keys", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 91 + }, + "id": 184, + "options": { + "legend": { + "calcs": [ + "mean", + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(delta(cache_total{result=\"hit\",name=\"rpc\",instance=~\"$instance\"}[1m]))/sum(delta(cache_total{name=\"rpc\",instance=~\"$instance\"}[1m])) ", + "hide": false, + "interval": "", + "legendFormat": "hit rate: {{ instance }} ", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "sum(delta(cache_code_total{result=\"hit\",name=\"rpc\",instance=~\"$instance\"}[1m]))/sum(delta(cache_code_total{name=\"rpc\",instance=~\"$instance\"}[1m])) ", + "hide": false, + "interval": "", + "legendFormat": "code hit rate: {{ instance }} ", + "refId": "B" + } + ], + "title": "Cache hit-rate", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 97 + }, + "id": 75, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "refId": "A" + } + ], + "title": "Network", + "type": "row" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 98 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(p2p_ingress{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "ingress: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "exemplar": true, + "expr": "rate(p2p_egress{instance=~\"$instance\"}[$rate_interval])", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "egress: {{instance}}", + "refId": "C" + } + ], + "title": "Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 98 + }, + "id": 77, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "expr": "p2p_peer_count{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "peers: {{instance}}-{{state}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "rate(p2p_dials{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "dials: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus" + }, + "expr": "rate(p2p_serves{instance=~\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "serves: {{instance}}", + "refId": "C" + } + ], + "title": "Peers", + "type": "timeseries" + } + ], + "refresh": "30s", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "0.97", + "value": "0.97" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "quantile", + "options": [ + { + "selected": false, + "text": "0.0", + "value": "0.0" + }, + { + "selected": false, + "text": "0.25", + "value": "0.25" + }, + { + "selected": false, + "text": "0.5", + "value": "0.5" + }, + { + "selected": false, + "text": "0.9", + "value": "0.9" + }, + { + "selected": true, + "text": "0.97", + "value": "0.97" + }, + { + "selected": false, + "text": "0.99", + "value": "0.99" + }, + { + "selected": false, + "text": "1", + "value": "1" + } + ], + "query": "0.0,0.25,0.5, 0.9, 0.97, 0.99, 1", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "go_goroutines", + "hide": 0, + "includeAll": true, + "label": "instance", + "multi": true, + "name": "instance", + "options": [], + "query": { + "query": "go_goroutines", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/.*instance=\"([^\"]*).*/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "auto": false, + "auto_count": 30, + "auto_min": "10s", + "current": { + "selected": false, + "text": "10m", + "value": "10m" + }, + "hide": 0, + "label": "Rate Interval", + "name": "rate_interval", + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "3h", + "value": "3h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,3h,6h,12h,1d,7d,14d,30d", + "queryValue": "", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "astranet Internals", + "uid": "b42a61d7-02b1-416c-8ab4-b9c864356174", + "version": 19, + "weekStart": "" +} \ No newline at end of file diff --git a/deployments/prometheus/dashboards/dashboard.yml b/deployments/prometheus/dashboards/dashboard.yml new file mode 100644 index 0000000..d8eb2f6 --- /dev/null +++ b/deployments/prometheus/dashboards/dashboard.yml @@ -0,0 +1,24 @@ +apiVersion: 1 + +providers: + # an unique provider name + - name: 'ast' + # org id. will default to orgId 1 if not specified + orgId: 1 + # name of the dashboard folder. Required + folder: '' + # folder UID. will be automatically generated if not specified + folderUid: '' + # provider type. Required + type: file + # disable dashboard deletion + disableDeletion: false + # enable dashboard editing + editable: true + # how often Grafana will scan for changed dashboards + updateIntervalSeconds: 10 + # allow updating provisioned dashboards from the UI + allowUiUpdates: true + options: + # path to dashboard files on disk. Required + path: /etc/grafana/provisioning/dashboards \ No newline at end of file diff --git a/deployments/prometheus/datasources/prometheus.yml b/deployments/prometheus/datasources/prometheus.yml new file mode 100644 index 0000000..d5dba38 --- /dev/null +++ b/deployments/prometheus/datasources/prometheus.yml @@ -0,0 +1,50 @@ +# config file version +apiVersion: 1 + +# list of datasources that should be deleted from the database +deleteDatasources: + - name: Prometheus + orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required + - name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + url: http://prometheus:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username, if used + basicAuthUser: + # basic auth password, if used + basicAuthPassword: + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: "1.1" + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: "..." + tlsClientCert: "..." + tlsClientKey: "..." + version: 1 + # allow users to edit datasources from the UI. + editable: true \ No newline at end of file diff --git a/deployments/prometheus/grafana.ini b/deployments/prometheus/grafana.ini new file mode 100644 index 0000000..6585c06 --- /dev/null +++ b/deployments/prometheus/grafana.ini @@ -0,0 +1,756 @@ +app_mode = production + +#################################### Logging ########################## +[log] +# Either "console", "file", "syslog". Default is console and file +# Use space to separate multiple modes, e.g. "console file" +;mode = console file + +# Either "debug", "info", "warn", "error", "critical", default is "info" +level = warn + +# optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug +;filters = + +#################################### Paths #################################### +[paths] +# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) +;data = /var/lib/grafana + +# Temporary files in `data` directory older than given duration will be removed +;temp_data_lifetime = 24h + +# Directory where grafana can store logs +;logs = /var/log/grafana + +# Directory where grafana will automatically scan and look for plugins +;plugins = /var/lib/grafana/plugins + +# folder that contains provisioning config files that grafana will apply on startup and while running. +;provisioning = conf/provisioning + +#################################### Server #################################### +[server] +# Protocol (http, https, h2, socket) +;protocol = http + +# The ip address to bind to, empty will bind to all interfaces +;http_addr = + +# The http port to use +;http_port = 3000 + +# The public facing domain name used to access grafana from a browser +;domain = localhost + +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +;enforce_domain = false + +# The full public facing url you use in browser, used for redirects and emails +# If you use reverse proxy and sub path specify full url (with sub path) +;root_url = %(protocol)s://%(domain)s:%(http_port)s/ + +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +;serve_from_sub_path = false + +# Log web requests +;router_logging = false + +# the path relative working path +;static_root_path = public + +# enable gzip +;enable_gzip = false + +# https certs & key file +;cert_file = +;cert_key = + +# Unix socket path +;socket = + +#################################### Database #################################### +[database] +# You can configure the database connection by specifying type, host, name, user and password +# as separate properties or as on string using the url properties. + +# Either "mysql", "postgres" or "sqlite3", it's your choice +;type = sqlite3 +;host = 127.0.0.1:3306 +;name = grafana +;user = root +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +;password = + +# Use either URL or the previous fields to configure the database +# Example: mysql://user:secret@host:port/database +;url = + +# For "postgres" only, either "disable", "require" or "verify-full" +;ssl_mode = disable + +;ca_cert_path = +;client_key_path = +;client_cert_path = +;server_cert_name = + +# For "sqlite3" only, path relative to data_path setting +;path = grafana.db + +# Max idle conn setting default is 2 +;max_idle_conn = 2 + +# Max conn setting default is 0 (mean not set) +;max_open_conn = + +# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours) +;conn_max_lifetime = 14400 + +# Set to true to log the sql calls and execution times. +;log_queries = + +# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared) +;cache_mode = private + +#################################### Cache server ############################# +[remote_cache] +# Either "redis", "memcached" or "database" default is "database" +;type = database + +# cache connectionstring options +# database: will use Grafana primary database. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# memcache: 127.0.0.1:11211 +;connstr = + +#################################### Data proxy ########################### +[dataproxy] + +# This enables data proxy logging, default is false +;logging = false + +# How long the data proxy waits before timing out, default is 30 seconds. +# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set. +;timeout = 30 + +# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false. +;send_user_header = false + +#################################### Analytics #################################### +[analytics] +# Server reporting, sends usage counters to stats.grafana.org every 24 hours. +# No ip addresses are being tracked, only simple counters to track +# running instances, dashboard and error counts. It is very helpful to us. +# Change this option to false to disable reporting. +;reporting_enabled = true + +# Set to false to disable all checks to https://grafana.net +# for new versions (grafana itself and plugins), check is used +# in some UI views to notify that grafana or plugin update exists +# This option does not cause any auto updates, nor send any information +# only a GET request to http://grafana.com to get latest versions +;check_for_updates = true + +# Google Analytics universal tracking code, only enabled if you specify an id here +;google_analytics_ua_id = + +# Google Tag Manager ID, only enabled if you specify an id here +;google_tag_manager_id = + +#################################### Security #################################### +[security] +# disable creation of admin user on first start of grafana +;disable_initial_admin_creation = false + +# default admin user, created on startup +;admin_user = admin + +# default admin password, can be changed before first start of grafana, or in profile settings +;admin_password = admin + +# used for signing +;secret_key = SW2YcwTIb9zpOOhoPsMm + +# disable gravatar profile images +;disable_gravatar = false + +# data source proxy whitelist (ip_or_domain:port separated by spaces) +;data_source_proxy_whitelist = + +# disable protection against brute force login attempts +;disable_brute_force_login_protection = false + +# set to true if you host Grafana behind HTTPS. default is false. +;cookie_secure = false + +# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" +;cookie_samesite = lax + +# set to true if you want to allow browsers to render Grafana in a ,