├── .dockerignore ├── .github └── workflows │ ├── check-build.yaml │ └── cla.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo-component.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── acl ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ └── tests.rs ├── cargo-generate.toml ├── core ├── Cargo.toml ├── README.md ├── src │ ├── lib.rs │ └── programs.rs └── wit ├── evm ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── examples ├── LICENSE.md ├── README.md ├── barebones-with-auxilary │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── barebones │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── basic-transaction │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── custom-hash │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── device-key-proxy │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── tests.rs ├── infinite-loop │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── oracle-example │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── private-acl │ ├── Cargo.toml │ ├── addresses.txt │ ├── build.rs │ └── src │ │ └── lib.rs ├── risczero-zkvm-verification │ ├── Cargo.toml │ ├── LICENSE.md │ ├── json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── core │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── methods │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── guest │ │ │ │ ├── Cargo.lock │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ │ └── main.rs │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── res │ │ │ └── example.json │ │ └── src │ │ │ └── main.rs │ ├── src │ │ └── lib.rs │ └── test_data │ │ ├── zkvm_image_id.bin │ │ ├── zkvm_receipt.bin │ │ └── zkvm_wrong_image_id.bin └── siwe │ ├── Cargo.toml │ ├── README.md │ └── src │ └── lib.rs ├── programs ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── runtime ├── Cargo.toml ├── README.md ├── src │ └── lib.rs ├── tests │ └── runtime.rs └── wit ├── rust-toolchain.toml ├── templates └── basic-template │ ├── .dockerignore │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── README.md │ ├── cli │ ├── Cargo.toml │ ├── entropy_metadata.scale │ └── src │ │ └── main.rs │ ├── generate-types │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── main.rs │ └── src │ ├── lib.rs │ └── tests.rs └── wit └── application.wit /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | .git 3 | -------------------------------------------------------------------------------- /.github/workflows/check-build.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Check build" 3 | on: ["push"] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Print version 11 | run: cargo --version 12 | - name: Run check 13 | run: cargo install cargo-risczero && cargo risczero install && cargo check -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | # This workflow automates the process of signing our CLA. It makes use of 2 | # the action at https://github.com/contributor-assistant/github-action in 3 | # order to provide automations. 4 | # 5 | # This workflow file should be present in every repository that wants to 6 | # use the Contributor License Agreement automation process. Ideally, it 7 | # would remain more-or-less synchronized across each repository as updates 8 | # are rolled out. 9 | # 10 | # Since the database of signatories is maintained in a remote repository, 11 | # each repository that wishes to make use of the CLA Assistant will also 12 | # need to have a repository secret (named `CLA_ASSISTANT_LITE_PAT`) that 13 | # grants permission to write to the "signatures" file in that repository. 14 | --- 15 | name: "CLA Assistant" 16 | on: 17 | issue_comment: 18 | types: 19 | - created 20 | pull_request_target: 21 | types: 22 | - opened 23 | - closed 24 | - synchronize 25 | 26 | # Explicitly configure permissions, in case the GITHUB_TOKEN workflow permissions 27 | # are set to read-only in the repository's settings. 28 | permissions: 29 | actions: write 30 | contents: read # We only need to `read` since signatures are in a remote repo. 31 | pull-requests: write 32 | statuses: write 33 | 34 | jobs: 35 | CLAAssistant: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: "CLA Assistant" 39 | if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' 40 | uses: entropyxyz/contributor-assistant-github-action@c5f4628ffe1edb97724edb64e0dd4795394d33e5 # exemptRepoOrgMembers 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | # Required, so that the bot in this repository has `write` permissions to Contents of remote repo. 44 | PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ASSISTANT_LITE_PAT }} 45 | with: 46 | path-to-signatures: 'legal/cla/v1/signatures.json' 47 | path-to-document: 'https://github.com/entropyxyz/.github/blob/main/legal/cla/v1/cla.md' 48 | branch: 'main' 49 | allowlist: dependabot[bot],entropyxyz 50 | exemptRepoOrgMembers: true 51 | remote-organization-name: entropyxyz 52 | remote-repository-name: .github 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for contributing to the project! Please consider the following in PRs. 4 | 5 | ## Conventions 6 | 7 | Packages should use the `entropy-programs` prefix, which stands for "Entropy Programs". For example, `entropy-programs-core` is the "Entropy Programs Core" package, and `entropy-programs-acl` is the "Entropy Programs Access Control List" package. 8 | -------------------------------------------------------------------------------- /Cargo-component.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by cargo-component. 2 | # It is not intended for manual editing. 3 | version = 1 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["programs", "core", "acl", "evm", "runtime", "examples/*"] 3 | exclude = ["templates/*", "examples/risczero-zkvm-verification"] 4 | resolver = "2" 5 | 6 | [workspace.dependencies] 7 | entropy-programs = { path = "programs", default-features = false } 8 | entropy-programs-core = { path = "core", default-features = false } 9 | entropy-programs-acl = { path = "acl", default-features = false } 10 | entropy-programs-evm = { path = "evm", default-features = false } 11 | entropy-programs-runtime = { path = "runtime", default-features = false } 12 | wit-bindgen = { version = "0.7.0", default_features = false } 13 | 14 | # strip debug info since that makes up a major part of Wasm blobs, see Wasm's `twiggy` 15 | [profile.release] 16 | strip = "debuginfo" 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is for building the example programs in this repo. 2 | FROM entropyxyz/build-entropy-programs:v0.0.1 AS base 3 | ARG PACKAGE=template-barebones 4 | 5 | WORKDIR /usr/src/programs 6 | COPY . . 7 | 8 | RUN cargo component build --release -p $PACKAGE --target wasm32-unknown-unknown 9 | 10 | FROM scratch AS binary 11 | COPY --from=base /usr/src/programs/target/wasm32-unknown-unknown/release/*.wasm / 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### GNU AFFERO GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 19 November 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | ### Preamble 12 | 13 | The GNU Affero General Public License is a free, copyleft license for 14 | software and other kinds of works, specifically designed to ensure 15 | cooperation with the community in the case of network server software. 16 | 17 | The licenses for most software and other practical works are designed 18 | to take away your freedom to share and change the works. By contrast, 19 | our General Public Licenses are intended to guarantee your freedom to 20 | share and change all versions of a program--to make sure it remains 21 | free software for all its users. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | them if you wish), that you receive source code or can get it if you 27 | want it, that you can change the software or use pieces of it in new 28 | free programs, and that you know you can do these things. 29 | 30 | Developers that use our General Public Licenses protect your rights 31 | with two steps: (1) assert copyright on the software, and (2) offer 32 | you this License which gives you legal permission to copy, distribute 33 | and/or modify the software. 34 | 35 | A secondary benefit of defending all users' freedom is that 36 | improvements made in alternate versions of the program, if they 37 | receive widespread use, become available for other developers to 38 | incorporate. Many developers of free software are heartened and 39 | encouraged by the resulting cooperation. However, in the case of 40 | software used on network servers, this result may fail to come about. 41 | The GNU General Public License permits making a modified version and 42 | letting the public access it on a server without ever releasing its 43 | source code to the public. 44 | 45 | The GNU Affero General Public License is designed specifically to 46 | ensure that, in such cases, the modified source code becomes available 47 | to the community. It requires the operator of a network server to 48 | provide the source code of the modified version running there to the 49 | users of that server. Therefore, public use of a modified version, on 50 | a publicly accessible server, gives the public access to the source 51 | code of the modified version. 52 | 53 | An older license, called the Affero General Public License and 54 | published by Affero, was designed to accomplish similar goals. This is 55 | a different license, not a version of the Affero GPL, but Affero has 56 | released a new version of the Affero GPL which permits relicensing 57 | under this license. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | ### TERMS AND CONDITIONS 63 | 64 | #### 0. Definitions 65 | 66 | "This License" refers to version 3 of the GNU Affero General Public 67 | License. 68 | 69 | "Copyright" also means copyright-like laws that apply to other kinds 70 | of works, such as semiconductor masks. 71 | 72 | "The Program" refers to any copyrightable work licensed under this 73 | License. Each licensee is addressed as "you". "Licensees" and 74 | "recipients" may be individuals or organizations. 75 | 76 | To "modify" a work means to copy from or adapt all or part of the work 77 | in a fashion requiring copyright permission, other than the making of 78 | an exact copy. The resulting work is called a "modified version" of 79 | the earlier work or a work "based on" the earlier work. 80 | 81 | A "covered work" means either the unmodified Program or a work based 82 | on the Program. 83 | 84 | To "propagate" a work means to do anything with it that, without 85 | permission, would make you directly or secondarily liable for 86 | infringement under applicable copyright law, except executing it on a 87 | computer or modifying a private copy. Propagation includes copying, 88 | distribution (with or without modification), making available to the 89 | public, and in some countries other activities as well. 90 | 91 | To "convey" a work means any kind of propagation that enables other 92 | parties to make or receive copies. Mere interaction with a user 93 | through a computer network, with no transfer of a copy, is not 94 | conveying. 95 | 96 | An interactive user interface displays "Appropriate Legal Notices" to 97 | the extent that it includes a convenient and prominently visible 98 | feature that (1) displays an appropriate copyright notice, and (2) 99 | tells the user that there is no warranty for the work (except to the 100 | extent that warranties are provided), that licensees may convey the 101 | work under this License, and how to view a copy of this License. If 102 | the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 105 | #### 1. Source Code 106 | 107 | The "source code" for a work means the preferred form of the work for 108 | making modifications to it. "Object code" means any non-source form of 109 | a work. 110 | 111 | A "Standard Interface" means an interface that either is an official 112 | standard defined by a recognized standards body, or, in the case of 113 | interfaces specified for a particular programming language, one that 114 | is widely used among developers working in that language. 115 | 116 | The "System Libraries" of an executable work include anything, other 117 | than the work as a whole, that (a) is included in the normal form of 118 | packaging a Major Component, but which is not part of that Major 119 | Component, and (b) serves only to enable use of the work with that 120 | Major Component, or to implement a Standard Interface for which an 121 | implementation is available to the public in source code form. A 122 | "Major Component", in this context, means a major essential component 123 | (kernel, window system, and so on) of the specific operating system 124 | (if any) on which the executable work runs, or a compiler used to 125 | produce the work, or an object code interpreter used to run it. 126 | 127 | The "Corresponding Source" for a work in object code form means all 128 | the source code needed to generate, install, and (for an executable 129 | work) run the object code and to modify the work, including scripts to 130 | control those activities. However, it does not include the work's 131 | System Libraries, or general-purpose tools or generally available free 132 | programs which are used unmodified in performing those activities but 133 | which are not part of the work. For example, Corresponding Source 134 | includes interface definition files associated with source files for 135 | the work, and the source code for shared libraries and dynamically 136 | linked subprograms that the work is specifically designed to require, 137 | such as by intimate data communication or control flow between those 138 | subprograms and other parts of the work. 139 | 140 | The Corresponding Source need not include anything that users can 141 | regenerate automatically from other parts of the Corresponding Source. 142 | 143 | The Corresponding Source for a work in source code form is that same 144 | work. 145 | 146 | #### 2. Basic Permissions 147 | 148 | All rights granted under this License are granted for the term of 149 | copyright on the Program, and are irrevocable provided the stated 150 | conditions are met. This License explicitly affirms your unlimited 151 | permission to run the unmodified Program. The output from running a 152 | covered work is covered by this License only if the output, given its 153 | content, constitutes a covered work. This License acknowledges your 154 | rights of fair use or other equivalent, as provided by copyright law. 155 | 156 | You may make, run and propagate covered works that you do not convey, 157 | without conditions so long as your license otherwise remains in force. 158 | You may convey covered works to others for the sole purpose of having 159 | them make modifications exclusively for you, or provide you with 160 | facilities for running those works, provided that you comply with the 161 | terms of this License in conveying all material for which you do not 162 | control copyright. Those thus making or running the covered works for 163 | you must do so exclusively on your behalf, under your direction and 164 | control, on terms that prohibit them from making any copies of your 165 | copyrighted material outside their relationship with you. 166 | 167 | Conveying under any other circumstances is permitted solely under the 168 | conditions stated below. Sublicensing is not allowed; section 10 makes 169 | it unnecessary. 170 | 171 | #### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 172 | 173 | No covered work shall be deemed part of an effective technological 174 | measure under any applicable law fulfilling obligations under article 175 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 176 | similar laws prohibiting or restricting circumvention of such 177 | measures. 178 | 179 | When you convey a covered work, you waive any legal power to forbid 180 | circumvention of technological measures to the extent such 181 | circumvention is effected by exercising rights under this License with 182 | respect to the covered work, and you disclaim any intention to limit 183 | operation or modification of the work as a means of enforcing, against 184 | the work's users, your or third parties' legal rights to forbid 185 | circumvention of technological measures. 186 | 187 | #### 4. Conveying Verbatim Copies 188 | 189 | You may convey verbatim copies of the Program's source code as you 190 | receive it, in any medium, provided that you conspicuously and 191 | appropriately publish on each copy an appropriate copyright notice; 192 | keep intact all notices stating that this License and any 193 | non-permissive terms added in accord with section 7 apply to the code; 194 | keep intact all notices of the absence of any warranty; and give all 195 | recipients a copy of this License along with the Program. 196 | 197 | You may charge any price or no price for each copy that you convey, 198 | and you may offer support or warranty protection for a fee. 199 | 200 | #### 5. Conveying Modified Source Versions 201 | 202 | You may convey a work based on the Program, or the modifications to 203 | produce it from the Program, in the form of source code under the 204 | terms of section 4, provided that you also meet all of these 205 | conditions: 206 | 207 | - a) The work must carry prominent notices stating that you modified 208 | it, and giving a relevant date. 209 | - b) The work must carry prominent notices stating that it is 210 | released under this License and any conditions added under 211 | section 7. This requirement modifies the requirement in section 4 212 | to "keep intact all notices". 213 | - c) You must license the entire work, as a whole, under this 214 | License to anyone who comes into possession of a copy. This 215 | License will therefore apply, along with any applicable section 7 216 | additional terms, to the whole of the work, and all its parts, 217 | regardless of how they are packaged. This License gives no 218 | permission to license the work in any other way, but it does not 219 | invalidate such permission if you have separately received it. 220 | - d) If the work has interactive user interfaces, each must display 221 | Appropriate Legal Notices; however, if the Program has interactive 222 | interfaces that do not display Appropriate Legal Notices, your 223 | work need not make them do so. 224 | 225 | A compilation of a covered work with other separate and independent 226 | works, which are not by their nature extensions of the covered work, 227 | and which are not combined with it such as to form a larger program, 228 | in or on a volume of a storage or distribution medium, is called an 229 | "aggregate" if the compilation and its resulting copyright are not 230 | used to limit the access or legal rights of the compilation's users 231 | beyond what the individual works permit. Inclusion of a covered work 232 | in an aggregate does not cause this License to apply to the other 233 | parts of the aggregate. 234 | 235 | #### 6. Conveying Non-Source Forms 236 | 237 | You may convey a covered work in object code form under the terms of 238 | sections 4 and 5, provided that you also convey the machine-readable 239 | Corresponding Source under the terms of this License, in one of these 240 | ways: 241 | 242 | - a) Convey the object code in, or embodied in, a physical product 243 | (including a physical distribution medium), accompanied by the 244 | Corresponding Source fixed on a durable physical medium 245 | customarily used for software interchange. 246 | - b) Convey the object code in, or embodied in, a physical product 247 | (including a physical distribution medium), accompanied by a 248 | written offer, valid for at least three years and valid for as 249 | long as you offer spare parts or customer support for that product 250 | model, to give anyone who possesses the object code either (1) a 251 | copy of the Corresponding Source for all the software in the 252 | product that is covered by this License, on a durable physical 253 | medium customarily used for software interchange, for a price no 254 | more than your reasonable cost of physically performing this 255 | conveying of source, or (2) access to copy the Corresponding 256 | Source from a network server at no charge. 257 | - c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | - d) Convey the object code by offering access from a designated 263 | place (gratis or for a charge), and offer equivalent access to the 264 | Corresponding Source in the same way through the same place at no 265 | further charge. You need not require recipients to copy the 266 | Corresponding Source along with the object code. If the place to 267 | copy the object code is a network server, the Corresponding Source 268 | may be on a different server (operated by you or a third party) 269 | that supports equivalent copying facilities, provided you maintain 270 | clear directions next to the object code saying where to find the 271 | Corresponding Source. Regardless of what server hosts the 272 | Corresponding Source, you remain obligated to ensure that it is 273 | available for as long as needed to satisfy these requirements. 274 | - e) Convey the object code using peer-to-peer transmission, 275 | provided you inform other peers where the object code and 276 | Corresponding Source of the work are being offered to the general 277 | public at no charge under subsection 6d. 278 | 279 | A separable portion of the object code, whose source code is excluded 280 | from the Corresponding Source as a System Library, need not be 281 | included in conveying the object code work. 282 | 283 | A "User Product" is either (1) a "consumer product", which means any 284 | tangible personal property which is normally used for personal, 285 | family, or household purposes, or (2) anything designed or sold for 286 | incorporation into a dwelling. In determining whether a product is a 287 | consumer product, doubtful cases shall be resolved in favor of 288 | coverage. For a particular product received by a particular user, 289 | "normally used" refers to a typical or common use of that class of 290 | product, regardless of the status of the particular user or of the way 291 | in which the particular user actually uses, or expects or is expected 292 | to use, the product. A product is a consumer product regardless of 293 | whether the product has substantial commercial, industrial or 294 | non-consumer uses, unless such uses represent the only significant 295 | mode of use of the product. 296 | 297 | "Installation Information" for a User Product means any methods, 298 | procedures, authorization keys, or other information required to 299 | install and execute modified versions of a covered work in that User 300 | Product from a modified version of its Corresponding Source. The 301 | information must suffice to ensure that the continued functioning of 302 | the modified object code is in no case prevented or interfered with 303 | solely because modification has been made. 304 | 305 | If you convey an object code work under this section in, or with, or 306 | specifically for use in, a User Product, and the conveying occurs as 307 | part of a transaction in which the right of possession and use of the 308 | User Product is transferred to the recipient in perpetuity or for a 309 | fixed term (regardless of how the transaction is characterized), the 310 | Corresponding Source conveyed under this section must be accompanied 311 | by the Installation Information. But this requirement does not apply 312 | if neither you nor any third party retains the ability to install 313 | modified object code on the User Product (for example, the work has 314 | been installed in ROM). 315 | 316 | The requirement to provide Installation Information does not include a 317 | requirement to continue to provide support service, warranty, or 318 | updates for a work that has been modified or installed by the 319 | recipient, or for the User Product in which it has been modified or 320 | installed. Access to a network may be denied when the modification 321 | itself materially and adversely affects the operation of the network 322 | or violates the rules and protocols for communication across the 323 | network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | #### 7. Additional Terms 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders 351 | of that material) supplement the terms of this License with terms: 352 | 353 | - a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | - b) Requiring preservation of specified reasonable legal notices or 356 | author attributions in that material or in the Appropriate Legal 357 | Notices displayed by works containing it; or 358 | - c) Prohibiting misrepresentation of the origin of that material, 359 | or requiring that modified versions of such material be marked in 360 | reasonable ways as different from the original version; or 361 | - d) Limiting the use for publicity purposes of names of licensors 362 | or authors of the material; or 363 | - e) Declining to grant rights under trademark law for use of some 364 | trade names, trademarks, or service marks; or 365 | - f) Requiring indemnification of licensors and authors of that 366 | material by anyone who conveys the material (or modified versions 367 | of it) with contractual assumptions of liability to the recipient, 368 | for any liability that these contractual assumptions directly 369 | impose on those licensors and authors. 370 | 371 | All other non-permissive additional terms are considered "further 372 | restrictions" within the meaning of section 10. If the Program as you 373 | received it, or any part of it, contains a notice stating that it is 374 | governed by this License along with a term that is a further 375 | restriction, you may remove that term. If a license document contains 376 | a further restriction but permits relicensing or conveying under this 377 | License, you may add to a covered work material governed by the terms 378 | of that license document, provided that the further restriction does 379 | not survive such relicensing or conveying. 380 | 381 | If you add terms to a covered work in accord with this section, you 382 | must place, in the relevant source files, a statement of the 383 | additional terms that apply to those files, or a notice indicating 384 | where to find the applicable terms. 385 | 386 | Additional terms, permissive or non-permissive, may be stated in the 387 | form of a separately written license, or stated as exceptions; the 388 | above requirements apply either way. 389 | 390 | #### 8. Termination 391 | 392 | You may not propagate or modify a covered work except as expressly 393 | provided under this License. Any attempt otherwise to propagate or 394 | modify it is void, and will automatically terminate your rights under 395 | this License (including any patent licenses granted under the third 396 | paragraph of section 11). 397 | 398 | However, if you cease all violation of this License, then your license 399 | from a particular copyright holder is reinstated (a) provisionally, 400 | unless and until the copyright holder explicitly and finally 401 | terminates your license, and (b) permanently, if the copyright holder 402 | fails to notify you of the violation by some reasonable means prior to 403 | 60 days after the cessation. 404 | 405 | Moreover, your license from a particular copyright holder is 406 | reinstated permanently if the copyright holder notifies you of the 407 | violation by some reasonable means, this is the first time you have 408 | received notice of violation of this License (for any work) from that 409 | copyright holder, and you cure the violation prior to 30 days after 410 | your receipt of the notice. 411 | 412 | Termination of your rights under this section does not terminate the 413 | licenses of parties who have received copies or rights from you under 414 | this License. If your rights have been terminated and not permanently 415 | reinstated, you do not qualify to receive new licenses for the same 416 | material under section 10. 417 | 418 | #### 9. Acceptance Not Required for Having Copies 419 | 420 | You are not required to accept this License in order to receive or run 421 | a copy of the Program. Ancillary propagation of a covered work 422 | occurring solely as a consequence of using peer-to-peer transmission 423 | to receive a copy likewise does not require acceptance. However, 424 | nothing other than this License grants you permission to propagate or 425 | modify any covered work. These actions infringe copyright if you do 426 | not accept this License. Therefore, by modifying or propagating a 427 | covered work, you indicate your acceptance of this License to do so. 428 | 429 | #### 10. Automatic Licensing of Downstream Recipients 430 | 431 | Each time you convey a covered work, the recipient automatically 432 | receives a license from the original licensors, to run, modify and 433 | propagate that work, subject to this License. You are not responsible 434 | for enforcing compliance by third parties with this License. 435 | 436 | An "entity transaction" is a transaction transferring control of an 437 | organization, or substantially all assets of one, or subdividing an 438 | organization, or merging organizations. If propagation of a covered 439 | work results from an entity transaction, each party to that 440 | transaction who receives a copy of the work also receives whatever 441 | licenses to the work the party's predecessor in interest had or could 442 | give under the previous paragraph, plus a right to possession of the 443 | Corresponding Source of the work from the predecessor in interest, if 444 | the predecessor has it or can get it with reasonable efforts. 445 | 446 | You may not impose any further restrictions on the exercise of the 447 | rights granted or affirmed under this License. For example, you may 448 | not impose a license fee, royalty, or other charge for exercise of 449 | rights granted under this License, and you may not initiate litigation 450 | (including a cross-claim or counterclaim in a lawsuit) alleging that 451 | any patent claim is infringed by making, using, selling, offering for 452 | sale, or importing the Program or any portion of it. 453 | 454 | #### 11. Patents 455 | 456 | A "contributor" is a copyright holder who authorizes use under this 457 | License of the Program or a work on which the Program is based. The 458 | work thus licensed is called the contributor's "contributor version". 459 | 460 | A contributor's "essential patent claims" are all patent claims owned 461 | or controlled by the contributor, whether already acquired or 462 | hereafter acquired, that would be infringed by some manner, permitted 463 | by this License, of making, using, or selling its contributor version, 464 | but do not include claims that would be infringed only as a 465 | consequence of further modification of the contributor version. For 466 | purposes of this definition, "control" includes the right to grant 467 | patent sublicenses in a manner consistent with the requirements of 468 | this License. 469 | 470 | Each contributor grants you a non-exclusive, worldwide, royalty-free 471 | patent license under the contributor's essential patent claims, to 472 | make, use, sell, offer for sale, import and otherwise run, modify and 473 | propagate the contents of its contributor version. 474 | 475 | In the following three paragraphs, a "patent license" is any express 476 | agreement or commitment, however denominated, not to enforce a patent 477 | (such as an express permission to practice a patent or covenant not to 478 | sue for patent infringement). To "grant" such a patent license to a 479 | party means to make such an agreement or commitment not to enforce a 480 | patent against the party. 481 | 482 | If you convey a covered work, knowingly relying on a patent license, 483 | and the Corresponding Source of the work is not available for anyone 484 | to copy, free of charge and under the terms of this License, through a 485 | publicly available network server or other readily accessible means, 486 | then you must either (1) cause the Corresponding Source to be so 487 | available, or (2) arrange to deprive yourself of the benefit of the 488 | patent license for this particular work, or (3) arrange, in a manner 489 | consistent with the requirements of this License, to extend the patent 490 | license to downstream recipients. "Knowingly relying" means you have 491 | actual knowledge that, but for the patent license, your conveying the 492 | covered work in a country, or your recipient's use of the covered work 493 | in a country, would infringe one or more identifiable patents in that 494 | country that you have reason to believe are valid. 495 | 496 | If, pursuant to or in connection with a single transaction or 497 | arrangement, you convey, or propagate by procuring conveyance of, a 498 | covered work, and grant a patent license to some of the parties 499 | receiving the covered work authorizing them to use, propagate, modify 500 | or convey a specific copy of the covered work, then the patent license 501 | you grant is automatically extended to all recipients of the covered 502 | work and works based on it. 503 | 504 | A patent license is "discriminatory" if it does not include within the 505 | scope of its coverage, prohibits the exercise of, or is conditioned on 506 | the non-exercise of one or more of the rights that are specifically 507 | granted under this License. You may not convey a covered work if you 508 | are a party to an arrangement with a third party that is in the 509 | business of distributing software, under which you make payment to the 510 | third party based on the extent of your activity of conveying the 511 | work, and under which the third party grants, to any of the parties 512 | who would receive the covered work from you, a discriminatory patent 513 | license (a) in connection with copies of the covered work conveyed by 514 | you (or copies made from those copies), or (b) primarily for and in 515 | connection with specific products or compilations that contain the 516 | covered work, unless you entered into that arrangement, or that patent 517 | license was granted, prior to 28 March 2007. 518 | 519 | Nothing in this License shall be construed as excluding or limiting 520 | any implied license or other defenses to infringement that may 521 | otherwise be available to you under applicable patent law. 522 | 523 | #### 12. No Surrender of Others' Freedom 524 | 525 | If conditions are imposed on you (whether by court order, agreement or 526 | otherwise) that contradict the conditions of this License, they do not 527 | excuse you from the conditions of this License. If you cannot convey a 528 | covered work so as to satisfy simultaneously your obligations under 529 | this License and any other pertinent obligations, then as a 530 | consequence you may not convey it at all. For example, if you agree to 531 | terms that obligate you to collect a royalty for further conveying 532 | from those to whom you convey the Program, the only way you could 533 | satisfy both those terms and this License would be to refrain entirely 534 | from conveying the Program. 535 | 536 | #### 13. Remote Network Interaction; Use with the GNU General Public License 537 | 538 | Notwithstanding any other provision of this License, if you modify the 539 | Program, your modified version must prominently offer all users 540 | interacting with it remotely through a computer network (if your 541 | version supports such interaction) an opportunity to receive the 542 | Corresponding Source of your version by providing access to the 543 | Corresponding Source from a network server at no charge, through some 544 | standard or customary means of facilitating copying of software. This 545 | Corresponding Source shall include the Corresponding Source for any 546 | work covered by version 3 of the GNU General Public License that is 547 | incorporated pursuant to the following paragraph. 548 | 549 | Notwithstanding any other provision of this License, you have 550 | permission to link or combine any covered work with a work licensed 551 | under version 3 of the GNU General Public License into a single 552 | combined work, and to convey the resulting work. The terms of this 553 | License will continue to apply to the part which is the covered work, 554 | but the work with which it is combined will remain governed by version 555 | 3 of the GNU General Public License. 556 | 557 | #### 14. Revised Versions of this License 558 | 559 | The Free Software Foundation may publish revised and/or new versions 560 | of the GNU Affero General Public License from time to time. Such new 561 | versions will be similar in spirit to the present version, but may 562 | differ in detail to address new problems or concerns. 563 | 564 | Each version is given a distinguishing version number. If the Program 565 | specifies that a certain numbered version of the GNU Affero General 566 | Public License "or any later version" applies to it, you have the 567 | option of following the terms and conditions either of that numbered 568 | version or of any later version published by the Free Software 569 | Foundation. If the Program does not specify a version number of the 570 | GNU Affero General Public License, you may choose any version ever 571 | published by the Free Software Foundation. 572 | 573 | If the Program specifies that a proxy can decide which future versions 574 | of the GNU Affero General Public License can be used, that proxy's 575 | public statement of acceptance of a version permanently authorizes you 576 | to choose that version for the Program. 577 | 578 | Later license versions may give you additional or different 579 | permissions. However, no additional obligations are imposed on any 580 | author or copyright holder as a result of your choosing to follow a 581 | later version. 582 | 583 | #### 15. Disclaimer of Warranty 584 | 585 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 586 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 587 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT 588 | WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT 589 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 590 | A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND 591 | PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 592 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 593 | CORRECTION. 594 | 595 | #### 16. Limitation of Liability 596 | 597 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 598 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR 599 | CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 600 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES 601 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT 602 | NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR 603 | LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM 604 | TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER 605 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 606 | 607 | #### 17. Interpretation of Sections 15 and 16 608 | 609 | If the disclaimer of warranty and limitation of liability provided 610 | above cannot be given local legal effect according to their terms, 611 | reviewing courts shall apply local law that most closely approximates 612 | an absolute waiver of all civil liability in connection with the 613 | Program, unless a warranty or assumption of liability accompanies a 614 | copy of the Program in return for a fee. 615 | 616 | END OF TERMS AND CONDITIONS 617 | 618 | ### How to Apply These Terms to Your New Programs 619 | 620 | If you develop a new program, and you want it to be of the greatest 621 | possible use to the public, the best way to achieve this is to make it 622 | free software which everyone can redistribute and change under these 623 | terms. 624 | 625 | To do so, attach the following notices to the program. It is safest to 626 | attach them to the start of each source file to most effectively state 627 | the exclusion of warranty; and each file should have at least the 628 | "copyright" line and a pointer to where the full notice is found. 629 | 630 | 631 | Copyright (C) 632 | 633 | This program is free software: you can redistribute it and/or modify 634 | it under the terms of the GNU Affero General Public License as 635 | published by the Free Software Foundation, either version 3 of the 636 | License, or (at your option) any later version. 637 | 638 | This program is distributed in the hope that it will be useful, 639 | but WITHOUT ANY WARRANTY; without even the implied warranty of 640 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 641 | GNU Affero General Public License for more details. 642 | 643 | You should have received a copy of the GNU Affero General Public License 644 | along with this program. If not, see . 645 | 646 | Also add information on how to contact you by electronic and paper 647 | mail. 648 | 649 | If your software can interact with users remotely through a computer 650 | network, you should also make sure that it provides a way for users to 651 | get its source. For example, if your program is a web application, its 652 | interface could display a "Source" link that leads users to an archive 653 | of the code. There are many ways you could offer source, and different 654 | solutions will be better for different programs; see section 13 for 655 | the specific requirements. 656 | 657 | You should also get your employer (if you work as a programmer) or 658 | school, if any, to sign a "copyright disclaimer" for the program, if 659 | necessary. For more information on this, and how to apply and follow 660 | the GNU AGPL, see . 661 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Vercel sets the `HOME` env var weirdly, so we define a few extra 2 | # things to make sure it installs okay. 3 | .PHONY: vercel-rustup 4 | vercel-rustup: 5 | curl --proto '=https' --tlsv1.2 \ 6 | --silent --show-error --fail https://sh.rustup.rs \ 7 | | RUSTUP_HOME=/vercel/.rustup HOME=/root sh -s -- -y 8 | cp -R /root/.cargo /vercel/.cargo 9 | 10 | # Installs `rustup` in a typical case. 11 | .PHONY: rustup 12 | rustup: 13 | curl --proto '=https' --tlsv1.2 \ 14 | --silent --show-error --fail https://sh.rustup.rs \ 15 | | sh -s -- -y 16 | 17 | .PHONY: rust 18 | rust: 19 | export PATH="${PATH}:${HOME}/.cargo/bin" rustup default stable \ 20 | && rustup show \ 21 | && cargo install --git https://github.com/bytecodealliance/cargo-component --locked cargo-component 22 | 23 | # This target is specifically for generating API documentation from 24 | # within a Vercel.com Project. It is used as the Projects `installCommand`. 25 | vercel-install-api-docs :: vercel-rustup rust 26 | mkdir -p /root/.ssh 27 | echo "Host github.com" > /root/.ssh/config 28 | echo " StrictHostKeyChecking no" >> /root/.ssh/config 29 | echo " IdentityFile /root/.ssh/id_ed25519" >> /root/.ssh/config 30 | printenv github_ssh_deploy_key > /root/.ssh/id_ed25519 31 | chmod 600 /root/.ssh/id_ed25519 32 | 33 | # The Vercel Project's `buildCommand` is defined here. 34 | vercel-build-api-docs :: 35 | export PATH="${PATH}:${HOME}/.cargo/bin" \ 36 | && cargo doc --release --no-deps 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Entropy Programs 2 | 3 | Entropy allows the creation of decentralized signing authorities. Signing authorities exist as WebAssembly programs that can ingest a signature request, and a valid request is signed via a threshold signature scheme from a set of at-stake validators. These requests might include cryptocurrency-based transaction requests, certificate signing requests, or other mediums for cryptographic authentication. 4 | 5 | This repository contains libraries, toolchains, utilities, and specifications for writing, configuring, building, and testing the Wasm-based applications for Entropy. Programs can be written in any language that compiles to WebAssembly, which includes all LLVM-supported languages like Rust, AssemblyScript, Zig, and C. All the examples in this repository are written in Rust. 6 | 7 | ## Prerequisites 8 | 9 | To edit and compile the programs in the repository, you will need the following tools: 10 | 11 | 1. The lastest stable Rust toolchain. This can be installed with: 12 | 13 | ```shell 14 | curl https://sh.rustup.rs -sSf | sh 15 | ``` 16 | 17 | 1. The [cargo-component v0.2.0](https://github.com/bytecodealliance/cargo-component#installation) extension; used for building Wasm components: 18 | 19 | ```shell 20 | cargo install cargo-component --version 0.2.0 21 | ``` 22 | 23 | 1. The [cargo-generate](https://github.com/cargo-generate/cargo-generate) extension; used to generate project templates: 24 | 25 | ```shell 26 | cargo install cargo-generate 27 | ``` 28 | 29 | 1. The [wasm-tools](https://github.com/bytecodealliance/wasm-tools#installation) package. This is used by `cargo-component`: 30 | 31 | ```shell 32 | cargo install wasm-tools 33 | ``` 34 | 35 | 1. Verify that you have everything installed by running: 36 | 37 | ```shell 38 | rustc --version && cargo-component --version && cargo-generate --version && wasm-tools --version 39 | ``` 40 | 41 | This should output something like: 42 | 43 | ```plaintext 44 | rustc 1.79.0 (129f3b996 2024-06-10) 45 | cargo-component 0.2.0 (wasi:aec4b25) 46 | cargo generate 0.21.1 47 | wasm-tools 1.211.1 48 | ``` 49 | 50 | 1. [Optional] Install [Docker](https://docs.docker.com/get-docker/) to run the associated Dockerfiles found within the repository. 51 | 52 | 53 | ### Basic length-check program 54 | 55 | An example of a barebones program can be found at [`examples/barebones/src/lib.rs`](./examples/barebones/src/lib.rs). This example does a simple check on the length of the message to be signed. You can compile the program by running: 56 | 57 | ```bash 58 | cargo component build --release -p template-barebones --target wasm32-unknown-unknown 59 | ``` 60 | 61 | This builds the program as a Wasm component at `target/wasm32-unknown-unknown/release/template_barebones.wasm`. 62 | 63 | ### Custody program with configuration 64 | 65 | This example validates that an an EVM transaction request recipient exists on a list of allow-listed addresses. It also uses a configuration which allows the user to modify the allow-listed addresses without having to recompile the program. 66 | 67 | You can compile the program by running: 68 | 69 | ```bash 70 | cargo component build --release -p example-basic-transaction --target wasm32-unknown-unknown 71 | ``` 72 | 73 | ## Writing your own programs 74 | 75 | You can get started with a template program using `cargo-generate`: 76 | 77 | ```bash 78 | cargo generate entropyxyz/programs --name my-program --tag testnet 79 | ``` 80 | 81 | Make sure to attach the `--tag testnet` argument. This tells Cargo to use the `testnet` tag in the `github.com/entropyxyz/core` repository. 82 | 83 | Your template program is now in the `./my-program` directory and ready to be edited. You can run tests as you would a normal rust project with `cargo test`. 84 | 85 | You can compile your program with `cargo component`: 86 | 87 | You can generate your types by `cargo run generate-types`. If you change the type names of `UserConfig` or `AuxData`, you will need to change those names in `generate-types`. 88 | 89 | ```bash 90 | cargo component build --release --target wasm32-unknown-unknown 91 | ``` 92 | 93 | If you want to make your program publicly available and open source, and make it possible for others to verify that the source code corresponds to the on-chain binary, you can build it with the Dockerfile included in the template: 94 | 95 | ```bash 96 | docker build --output=binary-dir . 97 | ``` 98 | 99 | This will compile your program and put the `.wasm` binary file in `./binary-dir`. 100 | 101 | ## Running Tests 102 | 103 | Before running the runtime tests, you need to build the `template-barebones`, `infinite-loop` and `example-custom-hash` components. To do this, execute: 104 | 105 | ```bash 106 | cargo component build --release -p template-barebones -p infinite-loop -p example-custom-hash --target wasm32-unknown-unknown 107 | ``` 108 | 109 | This will create the components in `target/wasm32-unknown-unknown/release/`. 110 | 111 | ## Licensing 112 | 113 | For the most part, the code in this repository is licensed under [AGPL-3.0](./LICENSE). 114 | 115 | There are some exceptions however: 116 | 117 | - The original code in the `examples/risc0-zkvm-verification` crate comes from RISC Zero's [`risc0`](https://github.com/risc0/risc0) project, which is licensed under 118 | `Apache License 2.0`. 119 | 120 | Modifications made by Entropy to these crates are licensed under `AGPL-3.0`. 121 | -------------------------------------------------------------------------------- /acl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entropy-programs-acl" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "AGPL-3.0-or-later" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | entropy-programs-core = { path = "../core", default-features = false } 12 | entropy-programs-evm = { path = "../evm", default-features = false, optional = true } 13 | 14 | serde = { version = "1.0", default-features = false } 15 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 16 | "max-encoded-len", 17 | ] } 18 | scale-info = { version = "2.1.0", default-features = false } 19 | 20 | [features] 21 | default = ["std"] 22 | evm = ["dep:entropy-programs-evm"] 23 | std = ["entropy-programs-core/std", "codec/std", "evm"] 24 | -------------------------------------------------------------------------------- /acl/README.md: -------------------------------------------------------------------------------- 1 | # `entropy-programs-acl` 2 | 3 | Provides access control list functionality over generic architectures. 4 | -------------------------------------------------------------------------------- /acl/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use alloc::vec::Vec; 4 | use core::fmt::Debug; 5 | 6 | use codec::MaxEncodedLen; 7 | use codec::{Decode, Encode}; 8 | pub use entropy_programs_core::{Architecture, Error as CoreError, SatisfiableForArchitecture}; 9 | 10 | #[cfg(feature = "evm")] 11 | pub use entropy_programs_evm::{Evm, NameOrAddress, H160}; 12 | 13 | use scale_info::TypeInfo; 14 | use serde::{Deserialize, Serialize}; 15 | 16 | /// An access control list (Allow/Deny lists). 17 | #[derive( 18 | Clone, 19 | Debug, 20 | Encode, 21 | Decode, 22 | PartialEq, 23 | Eq, 24 | scale_info::TypeInfo, 25 | MaxEncodedLen, 26 | Serialize, 27 | Deserialize, 28 | )] 29 | pub struct Acl
{ 30 | pub addresses: Vec
, 31 | pub kind: AclKind, 32 | pub allow_null_recipient: bool, 33 | } 34 | 35 | /// Represents either an allow or deny list. 36 | #[derive( 37 | Debug, 38 | Clone, 39 | Copy, 40 | PartialEq, 41 | Eq, 42 | Encode, 43 | Decode, 44 | TypeInfo, 45 | MaxEncodedLen, 46 | Serialize, 47 | Deserialize, 48 | )] 49 | pub enum AclKind { 50 | Allow, 51 | Deny, 52 | } 53 | 54 | /// Creates an empty ACL that always evaluates to false. 55 | impl Default for Acl { 56 | fn default() -> Self { 57 | let addresses = Vec::::default(); 58 | Self { 59 | addresses, 60 | kind: AclKind::Allow, 61 | allow_null_recipient: false, 62 | } 63 | } 64 | } 65 | 66 | // TODO This needs to be generic over any architecture (use GetRecipient and GetSender traits) 67 | // TODO Move to `entropy-programs-evm` crate? 68 | #[allow(clippy::needless_collect)] 69 | #[cfg(feature = "evm")] 70 | impl SatisfiableForArchitecture for Acl<::AddressRaw> { 71 | fn is_satisfied_by( 72 | self, 73 | tx: &::TransactionRequest, 74 | ) -> Result<(), CoreError> { 75 | if tx.to.is_none() { 76 | return match self.allow_null_recipient { 77 | true => Ok(()), 78 | false => Err(CoreError::Evaluation( 79 | "Null recipients are not allowed.".to_string(), 80 | )), 81 | }; 82 | } 83 | 84 | let converted_addresses: Vec = self 85 | .addresses 86 | .into_iter() 87 | .map(|a| NameOrAddress::Address(H160::from(a))) 88 | .collect(); 89 | 90 | match ( 91 | converted_addresses.contains(&tx.to.clone().unwrap()), 92 | self.kind, 93 | ) { 94 | (true, AclKind::Allow) => Ok(()), 95 | (false, AclKind::Deny) => Ok(()), 96 | _ => Err(CoreError::Evaluation( 97 | "Transaction not allowed.".to_string(), 98 | )), 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /acl/src/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use entropy_programs_core::{Acl, AclKind}; 4 | use ethers_core::types::{NameOrAddress, TransactionRequest, H160}; 5 | 6 | use crate::Evaluate; 7 | 8 | #[test] 9 | fn test_acl_functions_properly() { 10 | let evm_address_1: [u8; 20] = [1u8; 20]; 11 | let evm_address_2: [u8; 20] = [2u8; 20]; 12 | let evm_address_3: [u8; 20] = [3u8; 20]; 13 | 14 | let to_address_1_tx = TransactionRequest { 15 | to: Some(NameOrAddress::Address(H160::from(evm_address_1))), 16 | ..Default::default() 17 | }; 18 | let to_address_2_tx = TransactionRequest { 19 | to: Some(NameOrAddress::Address(H160::from(evm_address_2))), 20 | ..Default::default() 21 | }; 22 | let to_address_3_tx = TransactionRequest { 23 | to: Some(NameOrAddress::Address(H160::from(evm_address_3))), 24 | ..Default::default() 25 | }; 26 | let to_null_recipient_tx = TransactionRequest { 27 | to: None, 28 | ..Default::default() 29 | }; 30 | 31 | let allowlisted_acl = Acl::<[u8; 20]> { 32 | addresses: vec![evm_address_1], 33 | ..Default::default() 34 | }; 35 | 36 | // should only let allowlisted_tx through 37 | assert!(allowlisted_acl 38 | .clone() 39 | .is_satisfied_by(to_address_1_tx.clone()) 40 | .is_ok()); 41 | 42 | assert!(allowlisted_acl 43 | .clone() 44 | .is_satisfied_by(to_address_2_tx.clone()) 45 | .is_err()); 46 | assert!(allowlisted_acl 47 | .clone() 48 | .is_satisfied_by(to_address_3_tx.clone()) 49 | .is_err()); 50 | assert!(allowlisted_acl 51 | .clone() 52 | .is_satisfied_by(to_null_recipient_tx.clone()) 53 | .is_err()); 54 | 55 | let denylisted_acl = Acl::<[u8; 20]> { 56 | addresses: vec![evm_address_1], 57 | kind: AclKind::Deny, 58 | ..Default::default() 59 | }; 60 | 61 | // should only block allowlisted and null recipient txs 62 | assert!(denylisted_acl 63 | .clone() 64 | .is_satisfied_by(to_address_2_tx.clone()) 65 | .is_ok()); 66 | assert!(denylisted_acl 67 | .clone() 68 | .is_satisfied_by(to_address_3_tx.clone()) 69 | .is_ok()); 70 | 71 | assert!(denylisted_acl 72 | .is_satisfied_by(to_address_1_tx.clone()) 73 | .is_err()); 74 | assert!(allowlisted_acl 75 | .is_satisfied_by(to_null_recipient_tx.clone()) 76 | .is_err()); 77 | 78 | let allowlisted_acl_with_null_recipient = Acl::<[u8; 20]> { 79 | addresses: vec![evm_address_1], 80 | allow_null_recipient: true, 81 | ..Default::default() 82 | }; 83 | 84 | // should only let allowlisted_tx and null recipient txs through 85 | assert!(allowlisted_acl_with_null_recipient 86 | .clone() 87 | .is_satisfied_by(to_address_1_tx.clone()) 88 | .is_ok()); 89 | assert!(allowlisted_acl_with_null_recipient 90 | .clone() 91 | .is_satisfied_by(to_null_recipient_tx.clone()) 92 | .is_ok()); 93 | 94 | assert!(allowlisted_acl_with_null_recipient 95 | .clone() 96 | .is_satisfied_by(to_address_2_tx.clone()) 97 | .is_err()); 98 | assert!(allowlisted_acl_with_null_recipient 99 | .is_satisfied_by(to_address_3_tx.clone()) 100 | .is_err()); 101 | 102 | let denylisted_acl_with_null_recipient = Acl::<[u8; 20]> { 103 | addresses: vec![evm_address_1], 104 | kind: AclKind::Deny, 105 | allow_null_recipient: true, 106 | }; 107 | 108 | // should only block allowlisted 109 | assert!(denylisted_acl_with_null_recipient 110 | .clone() 111 | .is_satisfied_by(to_address_2_tx.clone()) 112 | .is_ok()); 113 | assert!(denylisted_acl_with_null_recipient 114 | .clone() 115 | .is_satisfied_by(to_address_3_tx.clone()) 116 | .is_ok()); 117 | assert!(denylisted_acl_with_null_recipient 118 | .clone() 119 | .is_satisfied_by(to_null_recipient_tx.clone()) 120 | .is_ok()); 121 | 122 | assert!(denylisted_acl_with_null_recipient 123 | .is_satisfied_by(to_address_1_tx.clone()) 124 | .is_err()); 125 | 126 | let empty_acl = Acl::<[u8; 20]>::default(); 127 | 128 | // should fail all txs 129 | assert!(empty_acl.clone().is_satisfied_by(to_address_1_tx).is_err()); 130 | assert!(empty_acl.clone().is_satisfied_by(to_address_2_tx).is_err()); 131 | assert!(empty_acl.clone().is_satisfied_by(to_address_3_tx).is_err()); 132 | assert!(empty_acl.is_satisfied_by(to_null_recipient_tx).is_err()); 133 | } 134 | -------------------------------------------------------------------------------- /cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | sub_templates = ["templates/basic-template"] 3 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entropy-programs-core" 3 | version = "0.11.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "AGPL-3.0-or-later" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | description = "Core traits and types for building programs on the Entropy network." 10 | 11 | [dependencies] 12 | getrandom = { version = "0.2", default-features = false, features = ["custom"] } 13 | witgen = "0.15.0" 14 | 15 | # parsing 16 | serde = { version = "1.0", default-features = false, features = ["derive"] } 17 | wit-bindgen = { version = "0.7.0" } 18 | # wasmtime-wit-bindgen = { version = "10.0.1", default-features = false } 19 | # wasmtime-component-macro = { version = "10.0.1", default-features = false } 20 | # wasmtime ={ version = "10.0.1", default-features = false, features = ["component-model"] } 21 | 22 | [dev-dependencies] 23 | entropy-programs-acl = { path = "../acl", default-features = false, features = ["evm"] } 24 | entropy-programs-evm = { path = "../evm", default-features = false } 25 | 26 | [features] 27 | default = ["std"] 28 | std = ["serde/std"] 29 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # `entropy-programs-core` 2 | 3 | This contains core traits and types for writing modular programs code, including programs, runtimes, architectures (for writing architecture-agnostic programs and dynamic parsing) and signature-request interfaces. 4 | 5 | ## `.wit` 6 | 7 | User applications can generate and use the required WITs in two ways: 8 | 9 | 1. `cargo component` - prefered, since this doesn't require the user to build the wasm-component manually; 10 | 2. reexported from `entropy-programs-core` via `wit-bindgen` - this is a fallback for when `cargo component` is not available. 11 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This supports core traits and types for supporting new architectures and programs, and interfacing with them. 2 | 3 | /// See the [`wit-bindgen` Rust guest example](https://github.com/bytecodealliance/wit-bindgen#guest-rust) for information on how to use this. 4 | pub mod bindgen { 5 | wit_bindgen::generate!({ 6 | world: "program", 7 | macro_export 8 | }); 9 | } 10 | 11 | pub use bindgen::Error; 12 | 13 | pub mod programs; 14 | 15 | pub use architecture::*; 16 | pub use programs::*; 17 | 18 | /// Each transaction-like architecture should implement these. 19 | pub mod architecture { 20 | use super::bindgen::Error; 21 | use serde::{Deserialize, Serialize}; 22 | 23 | /// Trait for defining important types associated with an architecture. 24 | pub trait Architecture: Serialize + for<'de> Deserialize<'de> { 25 | /// Account type for that chain(SS58, H160, etc) 26 | type Address: Eq + Serialize + for<'de> Deserialize<'de> + From; 27 | /// Account type as it is stored in the database 28 | type AddressRaw: Eq + Serialize + for<'de> Deserialize<'de> + From; 29 | /// Transaction request type for unsigned transactions 30 | type TransactionRequest: GetSender 31 | + GetReceiver 32 | + Serialize 33 | + for<'de> Deserialize<'de> 34 | + Parse 35 | + TryParse; 36 | } 37 | 38 | /// Trait for getting the the sender of a transaction. 39 | pub trait GetSender { 40 | fn sender(&self) -> Option; 41 | } 42 | 43 | /// Trait for getting the the receiver of a transaction. 44 | pub trait GetReceiver { 45 | fn receiver(&self) -> Option; 46 | } 47 | 48 | /// DEPRECATED: Use `TryParse` 49 | /// 50 | /// Trait for parsing a raw transaction request into its native transaction request struct. 51 | pub trait Parse { 52 | fn parse(raw_tx: String) -> Result; 53 | } 54 | 55 | /// Tries to parse a raw transaction request into its native transaction request struct. 56 | pub trait TryParse { 57 | fn try_parse(raw_tx: &[u8]) -> Result; 58 | } 59 | } 60 | 61 | /// Includes items that should be imported into most scopes 62 | pub mod prelude { 63 | // reexport getrandom custom handler (move to macro) 64 | pub use getrandom::register_custom_getrandom; 65 | // reexport all core traits 66 | pub use super::architecture::*; 67 | 68 | use core::num::NonZeroU32; 69 | use getrandom::Error; 70 | 71 | /// Custom `getrandom()` handler that always returns an error. 72 | /// 73 | /// `getrandom` is a commonly used package for sourcing randomness.This should return an error for now, 74 | /// but in the future it might make sense for the validators to determinstically source randomness from 75 | /// BABE (eg. at a certain block) 76 | /// 77 | /// From https://docs.rs/getrandom/latest/getrandom/macro.register_custom_getrandom.html 78 | // TODO This should get throw into the macros 79 | pub fn always_fail(_buf: &mut [u8]) -> Result<(), Error> { 80 | let code = NonZeroU32::new(Error::CUSTOM_START.saturating_add(1)).unwrap(); 81 | Err(Error::from(code)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/src/programs.rs: -------------------------------------------------------------------------------- 1 | //! Contains traits that programs should implement, including Architecture-agnostic programs and generic programs. 2 | //! 3 | //! For runtime and binary size optimizations, program construction should be done at compile time by using `const` types, if possible. This can be done by using `const` generic parameters, 4 | //! or by using a `const` builder. Both methods are described nicely here: https://wapl.es/rust/2022/07/03/const-builder-pattern.html 5 | 6 | use crate::architecture::Architecture; 7 | use crate::bindgen::Error; 8 | 9 | /// Programs using binary (or other unserialized) data must implement this. This is the most barebones trait for programs. 10 | pub trait Satisfiable { 11 | /// Indicates that the data satisfies the constraint. 12 | fn is_satisfied_by(self, data: &[u8]) -> Result<(), Error>; 13 | } 14 | 15 | /// Any program using transaction-like/architecture-agnostic signature requests must implement this. 16 | /// 17 | /// For example, a program that checks that the recipient is not a blacklisted address would implement this trait for EVM, and would be similar to this: 18 | /// ``` 19 | /// use entropy_programs_acl::*; 20 | /// use entropy_programs_evm::*; 21 | /// 22 | /// let non_blacklisted_addr: [u8; 20] = [1u8; 20]; 23 | /// let blacklisted_addr_1: [u8; 20] = [2u8; 20]; 24 | /// let blacklisted_addr_2: [u8; 20] = [3u8; 20]; 25 | /// 26 | /// let no_malicious_addresses = Acl { 27 | /// addresses: vec![blacklisted_addr_1, blacklisted_addr_2], 28 | /// kind: AclKind::Deny, 29 | /// allow_null_recipient: false, 30 | /// }; 31 | /// 32 | /// let non_blacklisted_recipient_tx = EvmTransactionRequest { 33 | /// to: Some(NameOrAddress::Address(H160::from(non_blacklisted_addr))), 34 | /// ..Default::default() 35 | /// }; 36 | /// 37 | /// let blacklisted_recipient_tx = EvmTransactionRequest { 38 | /// to: Some(NameOrAddress::Address(H160::from(blacklisted_addr_1))), 39 | /// ..Default::default() 40 | /// }; 41 | /// 42 | /// // This will be allowed, since the recipient is not in the blacklisted ACL. 43 | /// no_malicious_addresses.clone().is_satisfied_by(&non_blacklisted_recipient_tx)?; 44 | /// // This will return an error, because the recipient is not in the ACL. 45 | /// assert!(no_malicious_addresses.is_satisfied_by(&blacklisted_recipient_tx).is_err()); 46 | /// Ok::<(), CoreError>(()) 47 | /// ``` 48 | /// 49 | pub trait SatisfiableForArchitecture { 50 | /// Indicates that the transaction request satisfies the constraint. 51 | fn is_satisfied_by(self, tx: &::TransactionRequest) -> Result<(), Error>; 52 | } 53 | -------------------------------------------------------------------------------- /core/wit: -------------------------------------------------------------------------------- 1 | ../wit -------------------------------------------------------------------------------- /evm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entropy-programs-evm" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "AGPL-3.0-or-later" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | entropy-programs-core = { path = "../core" } 12 | 13 | # EVM 14 | ethers-core = { version = "2.0.6", default-features = false } 15 | getrandom = { version = "0.2", default-features = false, optional = true } 16 | rlp = { version = "0.5.2", default-features = false } 17 | hex = "0.4.3" 18 | primitive-types = { version = "0.12.1", default-features = false, features = [ 19 | "scale-info", 20 | "serde_no_std", 21 | ] } 22 | serde = { version = "1.0", default-features = false } 23 | # ethereum = { version = "0.14.0", default-features = false, features = ["with-serde"]} 24 | 25 | [features] 26 | default = [] 27 | # std = ["rlp/std", "primitive-types/std", "getrandom", "getrandom/std", "ethereum/std"] 28 | std = ["rlp/std", "primitive-types/std"] 29 | -------------------------------------------------------------------------------- /evm/README.md: -------------------------------------------------------------------------------- 1 | # `entropy-programs-evm` 2 | 3 | Provides the EVM `Architecture`, adding EVM support to generic programs (not specific to Ethereum Mainnet). 4 | -------------------------------------------------------------------------------- /evm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the EVM architecture and its associated types. Since it implements Architecture, programs written around the Architecture trait can be used with EVM. 2 | 3 | extern crate alloc; 4 | 5 | use alloc::string::String; 6 | 7 | use entropy_programs_core::{ 8 | Architecture, Error as CoreError, GetReceiver, GetSender, Parse, TryParse, 9 | }; 10 | pub use ethers_core::types::transaction::request::TransactionRequest as EvmTransactionRequest; 11 | pub use ethers_core::types::{NameOrAddress, H160}; 12 | use rlp::Rlp; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// EVM Architecture 16 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 17 | pub struct Evm; 18 | 19 | impl Architecture for Evm { 20 | type Address = H160; 21 | type AddressRaw = [u8; 20]; 22 | type TransactionRequest = EvmTransactionRequest; 23 | } 24 | 25 | impl GetSender for ::TransactionRequest { 26 | fn sender(&self) -> Option<::Address> { 27 | self.from 28 | } 29 | } 30 | 31 | impl GetReceiver for ::TransactionRequest { 32 | fn receiver(&self) -> Option<::Address> { 33 | match &self.to { 34 | Some(to) => match to { 35 | NameOrAddress::Address(addr) => Some(addr.to_owned()), 36 | // This should never get returned because we Error on ENS names in the `parse` 37 | // function 38 | NameOrAddress::Name(_) => None, 39 | }, 40 | None => None, 41 | } 42 | } 43 | } 44 | 45 | impl Parse for ::TransactionRequest { 46 | fn parse( 47 | hex_rlp_raw_tx: String, 48 | ) -> Result<::TransactionRequest, CoreError> { 49 | let bytes = hex::decode(hex_rlp_raw_tx.replace("0x", "")).map_err(|e| { 50 | CoreError::InvalidSignatureRequest(format!("Unable to parse to RLP: {}", e)) 51 | })?; 52 | let rlp = Rlp::new(&bytes); 53 | match Self::decode_unsigned_rlp(&rlp) { 54 | Ok(tx) => match tx.to { 55 | // Clients shouldn't even be able to serialize tx reqs with ENS names, but it it 56 | // does somehow, err 57 | Some(NameOrAddress::Name(_)) => Err(CoreError::InvalidSignatureRequest( 58 | "ENS recipients not supported. Resolve to an address first.".to_string(), 59 | )), 60 | _ => Ok(tx), 61 | }, 62 | Err(e) => Err(CoreError::InvalidSignatureRequest(format!( 63 | "Unable to decode string: {}", 64 | e 65 | ))), 66 | } 67 | } 68 | } 69 | 70 | impl TryParse for ::TransactionRequest { 71 | /// TODO expect the hex-encoded RLP of the transaction request, so user doesn't have to hex::decode 72 | fn try_parse(bytes: &[u8]) -> Result { 73 | let request_as_string = String::from_utf8(bytes.to_owned()).map_err(|e| { 74 | CoreError::InvalidSignatureRequest(format!("Unable to parse to String: {}", e)) 75 | })?; 76 | let into_bytes = hex::decode(request_as_string.replace("0x", "")).map_err(|e| { 77 | CoreError::InvalidSignatureRequest(format!("Unable to parse to RLP: {}", e)) 78 | })?; 79 | let rlp = Rlp::new(&into_bytes); 80 | 81 | match Self::decode_unsigned_rlp(&rlp) { 82 | Ok(tx) => match tx.to { 83 | // Clients shouldn't even be able to serialize tx reqs with ENS names, but it it 84 | // does somehow, err 85 | Some(NameOrAddress::Name(_)) => Err(CoreError::InvalidSignatureRequest( 86 | "ENS recipients not supported. Resolve to an address first.".to_string(), 87 | )), 88 | _ => Ok(tx), 89 | }, 90 | Err(e) => Err(CoreError::InvalidSignatureRequest(format!( 91 | "Unable to decode string: {}", 92 | e 93 | ))), 94 | } 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use std::str::FromStr; 101 | 102 | use ethers_core::types::{Address as EvmAddress, H256}; 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn can_parse_evm_rlp_transactions() { 108 | // This is `serializedUnsignedTx` from entropy-js threshold-server tests 109 | let raw_unsigned_tx = "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080".to_string(); 110 | let unsigned_tx = EvmTransactionRequest::parse(raw_unsigned_tx).unwrap(); 111 | assert_eq!(unsigned_tx.sender(), None); 112 | assert_eq!( 113 | unsigned_tx.receiver(), 114 | Some(EvmAddress::from_str("772b9a9e8aa1c9db861c6611a82d251db4fac990").unwrap()) 115 | ); // manually removed the 0x 116 | } 117 | 118 | /// Tests that the parsed transaction's sighash matches the client's sighash 119 | #[test] 120 | fn evm_parsed_sighash_matches_clients_sighash() { 121 | // These are from from entropy-js threshold-server tests 122 | let raw_unsigned_tx = "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080".to_string(); 123 | let known_expected_sighash: H256 = H256::from_slice( 124 | hex::decode( 125 | "0xe62e139a15f27f3d5ba043756aaca2b6fe9597a95973befa36dbe6095ee16da2" 126 | .replace("0x", ""), 127 | ) 128 | .unwrap() 129 | .as_slice(), 130 | ); 131 | 132 | let unsigned_tx = EvmTransactionRequest::parse(raw_unsigned_tx).unwrap(); 133 | assert_eq!(unsigned_tx.sighash(), known_expected_sighash); 134 | } 135 | 136 | #[test] 137 | fn throws_error_parsing_malformed_evm_rlp() { 138 | let random_bytes = 139 | "0x1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080" 140 | .to_string(); 141 | 142 | let unsigned_tx = EvmTransactionRequest::parse(random_bytes); 143 | assert!(unsigned_tx.is_err()); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /examples/LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # `examples` 2 | 3 | This contains examples of programs. 4 | 5 | ## Setup 6 | 7 | Building a program requires `cargo component`, which is the cargo helper for building Wasm components. As a reminder, Entropy programs are simply Wasm components that satisfy an interface. 8 | 9 | ## Barebones Example 10 | 11 | `template-barebones` provides a basic example of a program that has the minimal dependencies to build a program (). It simply checks that the data to be signed is less than 10 bytes. 12 | 13 | ## Basic Transaction Example 14 | 15 | `template-basic-transaction` provides an example of how to constrain an Ethereum transaction request to a recipient in an access control list. 16 | 17 | ## Building Components 18 | 19 | To build the `barebones` component, run `cargo component build --release -p template-barebones --target wasm32-unknown-unknown`. This will create the files needed for testing at `target/wasm32-unknown-unknown/release/`. 20 | -------------------------------------------------------------------------------- /examples/barebones-with-auxilary/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-barebones-with-auxilary" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # This is required to compile programs to a wasm module and for use in rust libs 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [dependencies] 15 | entropy-programs-core = { workspace = true } 16 | schemars = {version = "0.8.16", optional = true} 17 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 18 | 19 | # These are used by `cargo component` 20 | [package.metadata.component] 21 | package = "entropy:example-barebones-with-auxilary" 22 | 23 | [package.metadata.component.target] 24 | path = "../../wit" 25 | 26 | [package.metadata.component.dependencies] 27 | 28 | [features] 29 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/barebones-with-auxilary/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates a contrieved program that can include auxilary data. Note, only the data in `message` will be signed by Entropy; `auxilary_data` is used to provide additional data (eg an additional signature or a zkp related to the preimage) that the user requires during program evaluation. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{string::ToString, vec::Vec}; 8 | 9 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 10 | 11 | // TODO confirm this isn't an issue for audit 12 | register_custom_getrandom!(always_fail); 13 | 14 | use serde::{Deserialize, Serialize}; 15 | 16 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 17 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 18 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 19 | pub struct UserConfig {} 20 | 21 | /// JSON representation of the auxiliary data 22 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 23 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 24 | pub struct AuxData {} 25 | 26 | pub struct BarebonesWithAuxilary; 27 | 28 | impl Program for BarebonesWithAuxilary { 29 | /// This is the only function required by the program runtime. `signature_request` includes the message to be 30 | /// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc. 31 | fn evaluate( 32 | signature_request: SignatureRequest, 33 | _config: Option>, 34 | _oracle_data: Option>>, 35 | ) -> Result<(), Error> { 36 | let SignatureRequest { 37 | message, 38 | auxilary_data, 39 | } = signature_request; 40 | 41 | // our program just checks that the length of the signature request is greater than 10 42 | if message.len() < 10 { 43 | return Err(Error::Evaluation( 44 | "Length of message is too short.".to_string(), 45 | )); 46 | } 47 | 48 | // Just check and make sure the `auxilary_data` field is not empty. 49 | auxilary_data.ok_or(Error::Evaluation( 50 | "This program requires that `auxilary_data` be `Some`.".to_string(), 51 | ))?; 52 | 53 | Ok(()) 54 | } 55 | 56 | /// Since we don't use a custom hash function, we can just return `None` here. 57 | fn custom_hash(_data: Vec) -> Option> { 58 | None 59 | } 60 | } 61 | 62 | export_program!(BarebonesWithAuxilary); 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | use alloc::vec; 68 | 69 | #[test] 70 | fn test_message_length_is_valid() { 71 | let signature_request = SignatureRequest { 72 | message: "some_data_longer_than_10_bytes".to_string().into_bytes(), 73 | auxilary_data: Some(vec![0x00]), 74 | }; 75 | 76 | assert!(BarebonesWithAuxilary::evaluate(signature_request, None, None).is_ok()); 77 | } 78 | 79 | /// Note, the program is written s.t. if `message` is less than 10 bytes, the program will error. 80 | #[test] 81 | fn test_message_length_is_invalid() { 82 | let signature_request = SignatureRequest { 83 | // should error since preimage is less than 10 bytes 84 | message: "under10".to_string().into_bytes(), 85 | auxilary_data: Some(vec![0x00]), 86 | }; 87 | 88 | assert!(BarebonesWithAuxilary::evaluate(signature_request, None, None).is_err()); 89 | } 90 | 91 | /// Note, the program is written s.t. if `auxilary_data` is `None`, the program will error. 92 | #[test] 93 | fn test_error_when_auxilary_field_is_none() { 94 | let signature_request = SignatureRequest { 95 | message: "some_data_longer_than_10_bytes".to_string().into_bytes(), 96 | // should error since auxilary_data field is None 97 | auxilary_data: None, 98 | }; 99 | 100 | assert!(BarebonesWithAuxilary::evaluate(signature_request, None, None).is_err()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/barebones/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-barebones" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | # This is required to compile programs to a wasm module and for use in rust libs 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | entropy-programs-core = { workspace = true } 18 | schemars = {version = "0.8.16", optional = true} 19 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 20 | 21 | # These are used by `cargo component` 22 | [package.metadata.component] 23 | package = "entropy:template-barebones" 24 | 25 | [package.metadata.component.target] 26 | path = "../../wit" 27 | 28 | [package.metadata.component.dependencies] 29 | 30 | 31 | [features] 32 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/barebones/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to write a contrieved and basic program: checking the length of the data to be signed. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{string::ToString, vec::Vec}; 8 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 12 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 13 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 14 | pub struct UserConfig {} 15 | 16 | /// JSON representation of the auxiliary data 17 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 18 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 19 | pub struct AuxData {} 20 | 21 | // TODO confirm this isn't an issue for audit 22 | register_custom_getrandom!(always_fail); 23 | 24 | pub struct BarebonesProgram; 25 | 26 | impl Program for BarebonesProgram { 27 | /// This is the only function required by the program runtime. `message` is the preimage of the curve element to be 28 | /// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc. 29 | fn evaluate( 30 | signature_request: SignatureRequest, 31 | _config: Option>, 32 | _oracle_data: Option>>, 33 | ) -> Result<(), Error> { 34 | let message: Vec = signature_request.message; 35 | 36 | // our program just checks that the length of the message is greater than 10 37 | if message.len() < 10 { 38 | return Err(Error::Evaluation( 39 | "Length of message is too short.".to_string(), 40 | )); 41 | } 42 | 43 | Ok(()) 44 | } 45 | 46 | /// Since we don't use a custom hash function, we can just return `None` here. 47 | fn custom_hash(_data: Vec) -> Option> { 48 | None 49 | } 50 | } 51 | 52 | export_program!(BarebonesProgram); 53 | 54 | // write a test that calls evaluate and passes it the proper parameters 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn test_should_sign() { 61 | let signature_request = SignatureRequest { 62 | message: "some_data_longer_than_10_bytes".to_string().into_bytes(), 63 | auxilary_data: None, 64 | }; 65 | 66 | assert!(BarebonesProgram::evaluate(signature_request, None, None).is_ok()); 67 | } 68 | 69 | #[test] 70 | fn test_should_error() { 71 | // data being checked is under 10 bytes in length 72 | let signature_request = SignatureRequest { 73 | message: "under10".to_string().into_bytes(), 74 | auxilary_data: None, 75 | }; 76 | 77 | assert!(BarebonesProgram::evaluate(signature_request, None, None).is_err()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/basic-transaction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-basic-transaction" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # This is required to compile programs to a wasm module and for use in rust libs 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [dependencies] 15 | entropy-programs = { workspace = true } 16 | # TODO move hex parsing into the entropy-programs-evm crate 17 | hex = { version = "0.4.3", default-features = false } 18 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 19 | serde_json = { version = "1.0", default-features = false, features = ["alloc"]} 20 | schemars = {version = "0.8.16", optional = true} 21 | # These are used by `cargo component` 22 | [package.metadata.component] 23 | package = "entropy:template-basic-transaction" 24 | 25 | [package.metadata.component.target] 26 | path = "../../wit" 27 | 28 | [package.metadata.component.dependencies] 29 | 30 | [features] 31 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/basic-transaction/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | extern crate alloc; 4 | 5 | use entropy_programs::{ 6 | core::{bindgen::*, export_program, prelude::*, SatisfiableForArchitecture, TryParse}, 7 | programs::acl::*, 8 | }; 9 | 10 | use alloc::{ 11 | format, 12 | string::{String, ToString}, 13 | vec::Vec, 14 | }; 15 | 16 | use serde::{Deserialize, Serialize}; 17 | use serde_json; 18 | 19 | pub struct BasicTransaction; 20 | 21 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 22 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 23 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 24 | pub struct UserConfig { 25 | pub allowlisted_addresses: Vec, 26 | } 27 | 28 | /// JSON representation of the auxiliary data 29 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 30 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 31 | pub struct AuxData {} 32 | 33 | // TODO confirm this isn't an issue for audit 34 | register_custom_getrandom!(always_fail); 35 | 36 | impl Program for BasicTransaction { 37 | /// This is the function that the programs engine will runtime esecute. signature_request is the preimage of the curve element to be 38 | /// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc. 39 | // #[no_mangle] 40 | fn evaluate( 41 | signature_request: SignatureRequest, 42 | config: Option>, 43 | _oracle_data: Option>>, 44 | ) -> Result<(), CoreError> { 45 | // parse the raw tx into some type supported by the Acl check 46 | let parsed_tx = ::TransactionRequest::try_parse( 47 | signature_request.message.as_slice(), 48 | )?; 49 | 50 | // construct a allowlist ACL from the config 51 | let typed_config = serde_json::from_slice::( 52 | config 53 | .ok_or(CoreError::Evaluation("No config provided.".to_string()))? 54 | .as_slice(), 55 | ) 56 | .map_err(|e| CoreError::Evaluation(format!("Failed to parse config: {}", e)))?; 57 | 58 | let addresses: Vec<::AddressRaw> = typed_config 59 | .allowlisted_addresses 60 | .iter() 61 | .map(|a| hex::decode(a).unwrap().try_into().unwrap()) 62 | .collect(); 63 | 64 | let allowlisted_acl = Acl::<::AddressRaw> { 65 | addresses, 66 | ..Default::default() 67 | }; 68 | 69 | // check that the parsed tx is allowed by the ACL 70 | allowlisted_acl.is_satisfied_by(&parsed_tx)?; 71 | 72 | Ok(()) 73 | } 74 | 75 | /// Since we don't use a custom hash function, we can just return `None` here. 76 | fn custom_hash(_data: Vec) -> Option> { 77 | None 78 | } 79 | } 80 | 81 | export_program!(BasicTransaction); 82 | 83 | // write a test that calls evaluate and passes it the proper parameters 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | 88 | const EVM_TX_WITH_ALLOWLISTED_RECIPIENT: &[u8] = b"0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080"; 89 | const EVM_TX_WITH_NONALLOWLISTED_RECIPIENT: &[u8] = b"0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac991019243726561746564204f6e20456e74726f7079018080"; 90 | const CONFIG: &[u8] = r#" 91 | { 92 | "allowlisted_addresses": [ 93 | "772b9a9e8aa1c9db861c6611a82d251db4fac990" 94 | ] 95 | } 96 | "# 97 | .as_bytes(); 98 | 99 | #[test] 100 | fn test_evaluate() { 101 | let signature_request = SignatureRequest { 102 | // `data` is an RLP serialized ETH transaction with the recipient set to `0x772b9a9e8aa1c9db861c6611a82d251db4fac990` 103 | message: EVM_TX_WITH_ALLOWLISTED_RECIPIENT.to_vec(), 104 | auxilary_data: None, 105 | }; 106 | 107 | assert!(BasicTransaction::evaluate(signature_request, Some(CONFIG.to_vec()), None).is_ok()); 108 | } 109 | 110 | #[test] 111 | fn test_start_fail() { 112 | let signature_request = SignatureRequest { 113 | // `data` is the same as previous test, but recipient address ends in `1` instead of `0`, so it should fail 114 | message: EVM_TX_WITH_NONALLOWLISTED_RECIPIENT.to_vec(), 115 | auxilary_data: None, 116 | }; 117 | 118 | assert!( 119 | BasicTransaction::evaluate(signature_request, Some(CONFIG.to_vec()), None).is_err() 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/custom-hash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-custom-hash" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # This is required to compile programs to a wasm module and for use in rust libs 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | blake3 = "1.5.0" 12 | entropy-programs-core = { workspace = true } 13 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 14 | schemars = {version = "0.8.16", optional = true} 15 | # These are used by `cargo component` 16 | [package.metadata.component] 17 | package = "entropy:example-custom-hash" 18 | 19 | [package.metadata.component.target] 20 | path = "../../wit" 21 | 22 | [package.metadata.component.dependencies] 23 | 24 | [features] 25 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/custom-hash/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to use a non-standardized or custom hash function in a program. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{string::ToString, vec::Vec}; 8 | 9 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | use blake3; 13 | 14 | // TODO confirm this isn't an issue for audit 15 | register_custom_getrandom!(always_fail); 16 | 17 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 18 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 19 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 20 | pub struct UserConfig {} 21 | 22 | /// JSON representation of the auxiliary data 23 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 24 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 25 | pub struct AuxData {} 26 | 27 | pub struct CustomHashExample; 28 | 29 | impl Program for CustomHashExample { 30 | fn evaluate( 31 | signature_request: SignatureRequest, 32 | _config: Option>, 33 | _oracle_data: Option>>, 34 | ) -> Result<(), Error> { 35 | if signature_request.message.len() < 1 { 36 | return Err(Error::Evaluation( 37 | "You need to give me SOME data to sign!".to_string(), 38 | )); 39 | } 40 | // By immediately returning Ok, we sign any data that is passed to us. 41 | Ok(()) 42 | } 43 | 44 | fn custom_hash(data: Vec) -> Option> { 45 | // We can use any hash function we want here, as long as it returns a 32 byte Vec. 46 | Some(blake3::hash(&data).as_bytes().to_vec()) 47 | } 48 | } 49 | 50 | export_program!(CustomHashExample); 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | 56 | #[test] 57 | /// We are just going to test that the custom hash function works WITHOUT calling evaluate 58 | fn test_custom_hash() { 59 | let message = "some_data_to_be_hashed".to_string().into_bytes(); 60 | 61 | let expected_hash = blake3::hash(&message).as_bytes().to_vec(); 62 | let actual_hash = CustomHashExample::custom_hash(message).unwrap(); 63 | 64 | assert_eq!(actual_hash, expected_hash); 65 | assert!(actual_hash.len() == 32); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/device-key-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "device-key-proxy" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | # This is required to compile programs to a wasm module and for use in rust libs 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | entropy-programs-core = { workspace = true } 18 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 19 | serde_json = { version = "1.0", default-features = false, features = ["alloc"]} 20 | base64 = { version = "0.22.0", default-features = false, features = ["alloc"] } 21 | k256 = { version = "0.13.3", default-features = false, features = ["ecdsa", "ecdsa-core"] } 22 | schnorrkel = { version = "0.11.4", default-features = false, features = ["std"]} 23 | ed25519-dalek = { version = "2.1.1", default-features = false, features = ["rand_core"]} 24 | schemars = {version = "0.8.16", optional = true} 25 | 26 | [dev-dependencies] 27 | rand_core = { version = "0.6.4", features = ["getrandom"] } 28 | 29 | # These are used by `cargo component` 30 | [package.metadata.component] 31 | package = "entropy:device-key-proxy" 32 | 33 | [package.metadata.component.target] 34 | path = "../../wit" 35 | 36 | [package.metadata.component.dependencies] 37 | 38 | [features] 39 | std = ["schemars"] 40 | -------------------------------------------------------------------------------- /examples/device-key-proxy/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | extern crate alloc; 4 | 5 | use alloc::{ 6 | format, 7 | string::{String, ToString}, 8 | vec::Vec, 9 | }; 10 | 11 | use entropy_programs_core::{bindgen::*, export_program, prelude::*, Error}; 12 | 13 | use base64::{prelude::BASE64_STANDARD, Engine}; 14 | use ed25519_dalek::{Signature as Ed25519Signature, VerifyingKey as Ed25519PublicKey}; 15 | use k256::ecdsa::{ 16 | signature::Verifier, Signature as EcdsaSignature, VerifyingKey as EcdsaPublicKey, 17 | }; 18 | use schnorrkel::{signing_context, PublicKey as Sr25519PublicKey, Signature as Sr25519Signature}; 19 | use serde::{Deserialize, Serialize}; 20 | 21 | #[cfg(test)] 22 | mod tests; 23 | 24 | // TODO confirm this isn't an issue for audit 25 | register_custom_getrandom!(always_fail); 26 | 27 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 28 | /// Note how this uses JSON-native types only. 29 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 30 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 31 | pub struct UserConfig { 32 | /// base64-encoded compressed point (33-byte) ECDSA public keys, (eg. "A572dqoue5OywY/48dtytQimL9WO0dpSObaFbAxoEWW9") 33 | pub ecdsa_public_keys: Option>, 34 | pub sr25519_public_keys: Option>, 35 | pub ed25519_public_keys: Option>, 36 | } 37 | 38 | /// Used by the program to verify signatures 39 | #[derive(Debug, Default, PartialEq, Eq, Clone)] 40 | pub struct Config { 41 | pub ecdsa_public_keys: Vec, 42 | pub sr25519_public_keys: Vec, 43 | pub ed25519_public_keys: Vec, 44 | } 45 | 46 | /// JSON representation of the auxiliary data 47 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 48 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 49 | pub struct AuxData { 50 | /// "ecdsa", "ed25519", "sr25519" 51 | pub public_key_type: String, 52 | /// base64-encoded public key 53 | pub public_key: String, 54 | /// base64-encoded signature 55 | pub signature: String, 56 | /// The context for the signature only needed in sr25519 signature type 57 | pub context: String, 58 | } 59 | 60 | trait DeviceKey { 61 | type PublicKey; 62 | type Signature; 63 | fn verify_signature(&self, message: &[u8], context: &[u8]) -> Result<(), Error>; 64 | fn from_base64(public_key: &[u8], signature: &[u8]) -> Result 65 | where 66 | Self: Sized; 67 | fn pub_key_from_base64(public_key: &[u8]) -> Result 68 | where 69 | Self: Sized; 70 | fn to_base64(&self) -> (String, String); 71 | // Checks that the public key is included in the config 72 | fn confirm_in_config(&self, config: &Config) -> Result<(), Error>; 73 | } 74 | 75 | struct VerificationParameters { 76 | pub_key: P, 77 | signature: S, 78 | } 79 | 80 | type Ecdsa = VerificationParameters; 81 | type Sr25519 = VerificationParameters; 82 | type Ed25519 = VerificationParameters; 83 | 84 | impl DeviceKey for Ecdsa { 85 | type PublicKey = EcdsaPublicKey; 86 | type Signature = EcdsaSignature; 87 | 88 | fn verify_signature(&self, message: &[u8], _context: &[u8]) -> Result<(), Error> { 89 | self.pub_key.verify(message, &self.signature).map_err(|_| { 90 | Error::InvalidSignatureRequest("Unable to verify ecdsa signature".to_string()) 91 | }) 92 | } 93 | 94 | fn from_base64(pub_key_encoded: &[u8], signature_encoded: &[u8]) -> Result { 95 | let pub_key = Ecdsa::pub_key_from_base64(pub_key_encoded)?; 96 | let signature = EcdsaSignature::from_slice( 97 | BASE64_STANDARD 98 | .decode(signature_encoded) 99 | .map_err(|_| Error::InvalidSignatureRequest("ecdsa from_base64 error".to_string()))? 100 | .as_slice(), 101 | ) 102 | .map_err(|_| Error::InvalidSignatureRequest("Invalid ecdsa signature".to_string()))?; 103 | Ok(Ecdsa { pub_key, signature }) 104 | } 105 | 106 | fn pub_key_from_base64(pub_key_encoded: &[u8]) -> Result { 107 | let pub_key = EcdsaPublicKey::from_sec1_bytes( 108 | BASE64_STANDARD 109 | .decode(pub_key_encoded) 110 | .map_err(|_| { 111 | Error::InvalidSignatureRequest("ecdsa pub_key_from_base64 error".to_string()) 112 | })? 113 | .as_slice(), 114 | ) 115 | .map_err(|_| Error::InvalidSignatureRequest("Invalid ecdsa public key".to_string()))?; 116 | Ok(pub_key) 117 | } 118 | 119 | fn to_base64(&self) -> (String, String) { 120 | let pub_key_encoded = BASE64_STANDARD.encode(self.pub_key.to_encoded_point(true)); 121 | let signature_encoded = BASE64_STANDARD.encode(self.signature.to_bytes()); 122 | (pub_key_encoded, signature_encoded) 123 | } 124 | 125 | fn confirm_in_config(&self, config: &Config) -> Result<(), Error> { 126 | if !config.ecdsa_public_keys.contains(&self.pub_key) { 127 | return Err(Error::InvalidSignatureRequest( 128 | "ECDSA Public key not in config".to_string(), 129 | )); 130 | } 131 | Ok(()) 132 | } 133 | } 134 | 135 | impl DeviceKey for Ed25519 { 136 | type PublicKey = Ed25519PublicKey; 137 | type Signature = Ed25519Signature; 138 | 139 | fn verify_signature(&self, message: &[u8], _context: &[u8]) -> Result<(), Error> { 140 | self.pub_key.verify(message, &self.signature).map_err(|_| { 141 | Error::InvalidSignatureRequest("Unable to verify ed25519 signature".to_string()) 142 | }) 143 | } 144 | 145 | fn pub_key_from_base64(public_key: &[u8]) -> Result 146 | where 147 | Self: Sized, 148 | { 149 | let pub_key = Ed25519PublicKey::try_from( 150 | BASE64_STANDARD 151 | .decode(public_key) 152 | .map_err(|_| { 153 | Error::InvalidSignatureRequest("ed25519 pub_key_from_base64 error".to_string()) 154 | })? 155 | .as_slice(), 156 | ) 157 | .map_err(|_| Error::InvalidSignatureRequest("Invalid ed25519 public key".to_string()))?; 158 | Ok(pub_key) 159 | } 160 | 161 | fn from_base64(pub_key_encoded: &[u8], signature_encoded: &[u8]) -> Result { 162 | let pub_key = Ed25519::pub_key_from_base64(pub_key_encoded)?; 163 | let signature = Ed25519Signature::try_from( 164 | BASE64_STANDARD 165 | .decode(signature_encoded) 166 | .map_err(|_| { 167 | Error::InvalidSignatureRequest("ed25519 from_base64 error".to_string()) 168 | })? 169 | .as_slice(), 170 | ) 171 | .unwrap(); 172 | Ok(Ed25519 { pub_key, signature }) 173 | } 174 | 175 | fn to_base64(&self) -> (String, String) { 176 | let pub_key_encoded = BASE64_STANDARD.encode(self.pub_key.to_bytes()); 177 | let signature_encoded = BASE64_STANDARD.encode(self.signature.to_bytes()); 178 | (pub_key_encoded, signature_encoded) 179 | } 180 | 181 | fn confirm_in_config(&self, config: &Config) -> Result<(), Error> { 182 | if !config.ed25519_public_keys.contains(&self.pub_key) { 183 | return Err(Error::InvalidSignatureRequest( 184 | "Ed25519 Public key not in config".to_string(), 185 | )); 186 | } 187 | Ok(()) 188 | } 189 | } 190 | 191 | impl DeviceKey for Sr25519 { 192 | type PublicKey = Sr25519PublicKey; 193 | type Signature = Sr25519Signature; 194 | 195 | fn verify_signature(&self, message: &[u8], context: &[u8]) -> Result<(), Error> { 196 | let context = signing_context(context); 197 | self.pub_key 198 | .verify(context.bytes(message), &self.signature) 199 | .map_err(|_| { 200 | Error::InvalidSignatureRequest("Unable to verify sr25519 signature".to_string()) 201 | }) 202 | } 203 | 204 | fn from_base64(pub_key_encoded: &[u8], signature_encoded: &[u8]) -> Result { 205 | let pub_key = Sr25519::pub_key_from_base64(pub_key_encoded)?; 206 | let signature = Sr25519Signature::from_bytes( 207 | BASE64_STANDARD 208 | .decode(signature_encoded) 209 | .map_err(|_| { 210 | Error::InvalidSignatureRequest("sr25519 from_base64 error".to_string()) 211 | })? 212 | .as_slice(), 213 | ) 214 | .map_err(|_| Error::InvalidSignatureRequest("Invalid sr25519 signature".to_string()))?; 215 | Ok(Sr25519 { pub_key, signature }) 216 | } 217 | 218 | fn pub_key_from_base64(pub_key_encoded: &[u8]) -> Result { 219 | let pub_key = Sr25519PublicKey::from_bytes( 220 | BASE64_STANDARD 221 | .decode(pub_key_encoded) 222 | .map_err(|_| { 223 | Error::InvalidSignatureRequest("sr25519 pub_key_from_base64 error".to_string()) 224 | })? 225 | .as_slice(), 226 | ) 227 | .map_err(|_| Error::InvalidSignatureRequest("Invalid sr25519 public key".to_string()))?; 228 | Ok(pub_key) 229 | } 230 | 231 | fn to_base64(&self) -> (String, String) { 232 | let pub_key_encoded = BASE64_STANDARD.encode(self.pub_key); 233 | let signature_encoded = BASE64_STANDARD.encode(self.signature.to_bytes()); 234 | (pub_key_encoded, signature_encoded) 235 | } 236 | 237 | fn confirm_in_config(&self, config: &Config) -> Result<(), Error> { 238 | if !config.sr25519_public_keys.contains(&self.pub_key) { 239 | return Err(Error::InvalidSignatureRequest( 240 | "Sr25519 Public key not in config".to_string(), 241 | )); 242 | } 243 | Ok(()) 244 | } 245 | } 246 | 247 | pub struct DeviceKeyProxy; 248 | 249 | impl Program for DeviceKeyProxy { 250 | fn evaluate( 251 | signature_request: SignatureRequest, 252 | raw_config: Option>, 253 | _oracle_data: Option>>, 254 | ) -> Result<(), Error> { 255 | let config_json = serde_json::from_slice::( 256 | raw_config 257 | .ok_or(Error::Evaluation("No config provided.".to_string()))? 258 | .as_slice(), 259 | ) 260 | .map_err(|e| Error::Evaluation(format!("Failed to parse config: {}", e)))?; 261 | let aux_data_json = serde_json::from_slice::( 262 | signature_request 263 | .auxilary_data 264 | .ok_or(Error::InvalidSignatureRequest( 265 | "No auxilary_data provided".to_string(), 266 | ))? 267 | .as_slice(), 268 | ) 269 | .map_err(|e| { 270 | Error::InvalidSignatureRequest(format!("Failed to parse auxilary_data: {}", e)) 271 | })?; 272 | 273 | let config = Config::try_from(config_json)?; 274 | 275 | // assert that the key in the aux data is in the config, and verify signature 276 | match aux_data_json.public_key_type.as_str() { 277 | "ecdsa" => { 278 | let verification_parameters = Ecdsa::from_base64( 279 | aux_data_json.public_key.as_bytes(), 280 | aux_data_json.signature.as_bytes(), 281 | )?; 282 | verification_parameters.confirm_in_config(&config)?; 283 | verification_parameters 284 | .verify_signature(signature_request.message.as_slice(), b"")?; 285 | } 286 | "sr25519" => { 287 | let verification_parameters = Sr25519::from_base64( 288 | aux_data_json.public_key.as_bytes(), 289 | aux_data_json.signature.as_bytes(), 290 | )?; 291 | verification_parameters.confirm_in_config(&config)?; 292 | verification_parameters.verify_signature( 293 | signature_request.message.as_slice(), 294 | aux_data_json.context.as_bytes(), 295 | )?; 296 | } 297 | "ed25519" => { 298 | let verification_parameters = Ed25519::from_base64( 299 | aux_data_json.public_key.as_bytes(), 300 | aux_data_json.signature.as_bytes(), 301 | )?; 302 | verification_parameters.confirm_in_config(&config)?; 303 | verification_parameters 304 | .verify_signature(signature_request.message.as_slice(), b"")?; 305 | } 306 | _ => { 307 | return Err(Error::InvalidSignatureRequest( 308 | "Invalid public key type".to_string(), 309 | )) 310 | } 311 | } 312 | 313 | Ok(()) 314 | } 315 | 316 | fn custom_hash(_data: Vec) -> Option> { 317 | None 318 | } 319 | } 320 | 321 | impl TryFrom for Config { 322 | type Error = Error; 323 | 324 | fn try_from(config_json: UserConfig) -> Result { 325 | let mut config = Config::default(); 326 | 327 | if let Some(ecdsa_pub_keys) = config_json.ecdsa_public_keys { 328 | for encoded_key in ecdsa_pub_keys { 329 | config.ecdsa_public_keys.push( 330 | Ecdsa::pub_key_from_base64(encoded_key.as_bytes()).map_err(|_| { 331 | Error::InvalidSignatureRequest("config conversion ecdsa".to_string()) 332 | })?, 333 | ); 334 | } 335 | } 336 | 337 | if let Some(sr25519_pub_keys) = config_json.sr25519_public_keys { 338 | for encoded_key in sr25519_pub_keys { 339 | let public_key = 340 | Sr25519::pub_key_from_base64(encoded_key.as_bytes()).map_err(|_| { 341 | Error::InvalidSignatureRequest("config conversion sr25519".to_string()) 342 | })?; 343 | config.sr25519_public_keys.push(public_key); 344 | } 345 | } 346 | 347 | if let Some(ed25519_pub_keys) = config_json.ed25519_public_keys { 348 | for encoded_key in ed25519_pub_keys { 349 | let public_key = 350 | Ed25519::pub_key_from_base64(encoded_key.as_bytes()).map_err(|_| { 351 | Error::InvalidSignatureRequest("config conversion ed25519".to_string()) 352 | })?; 353 | config.ed25519_public_keys.push(public_key); 354 | } 355 | } 356 | 357 | Ok(config) 358 | } 359 | } 360 | 361 | impl From for UserConfig { 362 | fn from(config: Config) -> UserConfig { 363 | let ecdsa_public_keys = config 364 | .ecdsa_public_keys 365 | .iter() 366 | .map(|key| { 367 | let encoded_key = BASE64_STANDARD.encode(key.to_encoded_point(true).as_bytes()); 368 | encoded_key 369 | }) 370 | .collect(); 371 | let sr25519_public_keys = config 372 | .sr25519_public_keys 373 | .iter() 374 | .map(|key| { 375 | let encoded_key = BASE64_STANDARD.encode(key); 376 | encoded_key 377 | }) 378 | .collect(); 379 | let ed25519_public_keys = config 380 | .ed25519_public_keys 381 | .iter() 382 | .map(|key| { 383 | let encoded_key = BASE64_STANDARD.encode(key.as_bytes()); 384 | encoded_key 385 | }) 386 | .collect(); 387 | 388 | UserConfig { 389 | ecdsa_public_keys: Some(ecdsa_public_keys), 390 | sr25519_public_keys: Some(sr25519_public_keys), 391 | ed25519_public_keys: Some(ed25519_public_keys), 392 | } 393 | } 394 | } 395 | 396 | export_program!(DeviceKeyProxy); 397 | -------------------------------------------------------------------------------- /examples/device-key-proxy/src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use ed25519_dalek::{Signature as Ed25519Signature, SigningKey as Ed25519Keypair}; 4 | use k256::ecdsa::{signature::Signer, Signature as EcdsaSignature, SigningKey as EcdsaKeypair}; 5 | use rand_core::OsRng; 6 | use schnorrkel::{signing_context, Keypair as Sr25519Keypair, Signature as Sr25519Signature}; 7 | 8 | struct TestKeys { 9 | ecdsa_keys: Vec, 10 | sr25519_keys: Vec, 11 | ed25519_keys: Vec, 12 | } 13 | 14 | #[test] 15 | fn test_ok_for_only_device_key_signatures() { 16 | let device_keys = generate_test_keys(); 17 | 18 | let config = Config { 19 | ecdsa_public_keys: device_keys 20 | .ecdsa_keys 21 | .iter() 22 | .map(|key| EcdsaPublicKey::from(key)) 23 | .collect(), 24 | sr25519_public_keys: device_keys 25 | .sr25519_keys 26 | .iter() 27 | .map(|key| key.public) 28 | .collect(), 29 | ed25519_public_keys: device_keys 30 | .ed25519_keys 31 | .iter() 32 | .map(|key| key.verifying_key()) 33 | .collect(), 34 | }; 35 | let json_config = UserConfig::from(config.clone()); 36 | 37 | let message: &str = "this is some message that we want to sign if its from a valid device key"; 38 | 39 | // constrtuct signature request from device key (for positive test) 40 | let ecdsa_device_key_signature: EcdsaSignature = device_keys.ecdsa_keys[0] 41 | .try_sign(message.as_bytes()) 42 | .unwrap(); 43 | let device_key_aux_data_json_edcsa = AuxData { 44 | public_key_type: "ecdsa".to_string(), 45 | public_key: BASE64_STANDARD.encode( 46 | device_keys.ecdsa_keys[0] 47 | .verifying_key() 48 | .to_encoded_point(true) 49 | .as_bytes(), 50 | ), 51 | signature: BASE64_STANDARD.encode(ecdsa_device_key_signature.to_bytes()), 52 | context: "".to_string(), 53 | }; 54 | let mut request_from_device_key = SignatureRequest { 55 | message: message.to_string().into_bytes(), 56 | auxilary_data: Some( 57 | serde_json::to_string(&device_key_aux_data_json_edcsa) 58 | .unwrap() 59 | .into_bytes(), 60 | ), 61 | }; 62 | 63 | let config_bytes = serde_json::to_vec(&json_config).unwrap(); 64 | // positive for edcsa 65 | assert!(DeviceKeyProxy::evaluate( 66 | request_from_device_key.clone(), 67 | Some(config_bytes.clone()), 68 | None 69 | ) 70 | .is_ok()); 71 | // positive for sr25519 72 | let context = signing_context(b""); 73 | 74 | let sr25519_device_key_signature: Sr25519Signature = 75 | device_keys.sr25519_keys[0].sign(context.bytes(message.as_bytes())); 76 | 77 | let device_key_aux_data_json_sr25519 = AuxData { 78 | public_key_type: "sr25519".to_string(), 79 | public_key: BASE64_STANDARD.encode(device_keys.sr25519_keys[0].public), 80 | signature: BASE64_STANDARD.encode(sr25519_device_key_signature.to_bytes()), 81 | context: "".to_string(), 82 | }; 83 | request_from_device_key.auxilary_data = Some( 84 | serde_json::to_string(&device_key_aux_data_json_sr25519.clone()) 85 | .unwrap() 86 | .into_bytes(), 87 | ); 88 | assert!(DeviceKeyProxy::evaluate( 89 | request_from_device_key.clone(), 90 | Some(config_bytes.clone()), 91 | None 92 | ) 93 | .is_ok()); 94 | // positive for ed25519 95 | let ed25519_device_key_signature: Ed25519Signature = 96 | device_keys.ed25519_keys[0].sign(message.as_bytes()); 97 | let device_key_aux_data_json_ed25519 = AuxData { 98 | public_key_type: "ed25519".to_string(), 99 | public_key: BASE64_STANDARD.encode(device_keys.ed25519_keys[0].verifying_key()), 100 | signature: BASE64_STANDARD.encode(ed25519_device_key_signature.to_bytes()), 101 | context: "".to_string(), 102 | }; 103 | request_from_device_key.auxilary_data = Some( 104 | serde_json::to_string(&device_key_aux_data_json_ed25519) 105 | .unwrap() 106 | .into_bytes(), 107 | ); 108 | DeviceKeyProxy::evaluate(request_from_device_key, Some(config_bytes.clone()), None).unwrap(); 109 | } 110 | 111 | #[test] 112 | fn test_fail_bad_signatures() { 113 | let device_keys = generate_test_keys(); 114 | let non_device_keys = generate_test_keys(); 115 | 116 | let config = Config { 117 | ecdsa_public_keys: device_keys 118 | .ecdsa_keys 119 | .iter() 120 | .map(|key| EcdsaPublicKey::from(key)) 121 | .collect(), 122 | sr25519_public_keys: device_keys 123 | .sr25519_keys 124 | .iter() 125 | .map(|key| key.public) 126 | .collect(), 127 | ed25519_public_keys: device_keys 128 | .ed25519_keys 129 | .iter() 130 | .map(|key| key.verifying_key()) 131 | .collect(), 132 | }; 133 | let json_config = UserConfig::from(config.clone()); 134 | 135 | let message: &str = "this is some message that we want to sign if its from a valid device key"; 136 | let context = signing_context(b""); 137 | 138 | // constrtuct signature request from device key (for positive test) 139 | let ecdsa_non_device_key_signature: EcdsaSignature = non_device_keys.ecdsa_keys[0] 140 | .try_sign(message.as_bytes()) 141 | .unwrap(); 142 | 143 | let device_key_aux_data_json_edcsa = AuxData { 144 | public_key_type: "ecdsa".to_string(), 145 | public_key: BASE64_STANDARD.encode( 146 | device_keys.ecdsa_keys[0] 147 | .verifying_key() 148 | .to_encoded_point(true) 149 | .as_bytes(), 150 | ), 151 | signature: BASE64_STANDARD.encode(ecdsa_non_device_key_signature.to_bytes()), 152 | context: "".to_string(), 153 | }; 154 | let mut request_from_device_key = SignatureRequest { 155 | message: message.to_string().into_bytes(), 156 | auxilary_data: Some( 157 | serde_json::to_string(&device_key_aux_data_json_edcsa) 158 | .unwrap() 159 | .into_bytes(), 160 | ), 161 | }; 162 | 163 | let config_bytes = serde_json::to_vec(&json_config).unwrap(); 164 | // fail for edcsa 165 | assert_eq!( 166 | DeviceKeyProxy::evaluate( 167 | request_from_device_key.clone(), 168 | Some(config_bytes.clone()), 169 | None 170 | ) 171 | .unwrap_err() 172 | .to_string(), 173 | "Error::InvalidSignatureRequest(\"Unable to verify ecdsa signature\")" 174 | ); 175 | let sr25519_non_device_key_signature: Sr25519Signature = 176 | non_device_keys.sr25519_keys[0].sign(context.bytes(message.as_bytes())); 177 | // fail for sr25519 178 | let device_key_aux_data_json_sr25519 = AuxData { 179 | public_key_type: "sr25519".to_string(), 180 | public_key: BASE64_STANDARD.encode(device_keys.sr25519_keys[0].public), 181 | signature: BASE64_STANDARD.encode(sr25519_non_device_key_signature.to_bytes()), 182 | context: "".to_string(), 183 | }; 184 | request_from_device_key.auxilary_data = Some( 185 | serde_json::to_string(&device_key_aux_data_json_sr25519.clone()) 186 | .unwrap() 187 | .into_bytes(), 188 | ); 189 | assert_eq!( 190 | DeviceKeyProxy::evaluate( 191 | request_from_device_key.clone(), 192 | Some(config_bytes.clone()), 193 | None 194 | ) 195 | .unwrap_err() 196 | .to_string(), 197 | "Error::InvalidSignatureRequest(\"Unable to verify sr25519 signature\")" 198 | ); 199 | // fail for ed25519 200 | let ed25519_non_device_key_signature: Ed25519Signature = 201 | non_device_keys.ed25519_keys[0].sign(message.as_bytes()); 202 | let device_key_aux_data_json_ed25519 = AuxData { 203 | public_key_type: "ed25519".to_string(), 204 | public_key: BASE64_STANDARD.encode(device_keys.ed25519_keys[0].verifying_key()), 205 | signature: BASE64_STANDARD.encode(ed25519_non_device_key_signature.to_bytes()), 206 | context: "".to_string(), 207 | }; 208 | request_from_device_key.auxilary_data = Some( 209 | serde_json::to_string(&device_key_aux_data_json_ed25519) 210 | .unwrap() 211 | .into_bytes(), 212 | ); 213 | assert_eq!( 214 | DeviceKeyProxy::evaluate( 215 | request_from_device_key.clone(), 216 | Some(config_bytes.clone()), 217 | None 218 | ) 219 | .unwrap_err() 220 | .to_string(), 221 | "Error::InvalidSignatureRequest(\"Unable to verify ed25519 signature\")" 222 | ); 223 | } 224 | 225 | #[test] 226 | fn test_fails_pub_key_not_found() { 227 | let device_keys = generate_test_keys(); 228 | let non_device_keys = generate_test_keys(); 229 | 230 | let config = Config { 231 | ecdsa_public_keys: device_keys 232 | .ecdsa_keys 233 | .iter() 234 | .map(|key| EcdsaPublicKey::from(key)) 235 | .collect(), 236 | sr25519_public_keys: device_keys 237 | .sr25519_keys 238 | .iter() 239 | .map(|key| key.public) 240 | .collect(), 241 | ed25519_public_keys: device_keys 242 | .ed25519_keys 243 | .iter() 244 | .map(|key| key.verifying_key()) 245 | .collect(), 246 | }; 247 | let json_config = UserConfig::from(config.clone()); 248 | let config_bytes = serde_json::to_vec(&json_config).unwrap(); 249 | 250 | let message: &str = "this is some message that we want to sign if its from a valid device key"; 251 | // construct signature request from non-device key (for negative test) 252 | let ecdsa_non_device_key_signature: EcdsaSignature = non_device_keys.ecdsa_keys[0] 253 | .try_sign(message.as_bytes()) 254 | .unwrap(); 255 | let non_device_key_aux_data_json = AuxData { 256 | public_key_type: "ecdsa".to_string(), 257 | public_key: BASE64_STANDARD.encode( 258 | non_device_keys.ecdsa_keys[0] 259 | .verifying_key() 260 | .to_encoded_point(true) 261 | .as_bytes(), 262 | ), 263 | signature: BASE64_STANDARD.encode(ecdsa_non_device_key_signature.to_bytes()), 264 | context: "".to_string(), 265 | }; 266 | let mut request_from_non_device_key = SignatureRequest { 267 | message: message.to_string().into_bytes(), 268 | auxilary_data: Some( 269 | serde_json::to_string(&non_device_key_aux_data_json) 270 | .unwrap() 271 | .into_bytes(), 272 | ), 273 | }; 274 | assert_eq!( 275 | DeviceKeyProxy::evaluate( 276 | request_from_non_device_key.clone(), 277 | Some(config_bytes.clone()), 278 | None 279 | ) 280 | .unwrap_err() 281 | .to_string(), 282 | "Error::InvalidSignatureRequest(\"ECDSA Public key not in config\")" 283 | ); 284 | //sr25519 fail 285 | let context = signing_context(b""); 286 | 287 | let sr25519_device_key_signature: Sr25519Signature = 288 | non_device_keys.sr25519_keys[0].sign(context.bytes(message.as_bytes())); 289 | 290 | let non_device_key_aux_data_json_sr25519 = AuxData { 291 | public_key_type: "sr25519".to_string(), 292 | public_key: BASE64_STANDARD.encode(non_device_keys.sr25519_keys[0].public), 293 | signature: BASE64_STANDARD.encode(sr25519_device_key_signature.to_bytes()), 294 | context: "".to_string(), 295 | }; 296 | request_from_non_device_key.auxilary_data = Some( 297 | serde_json::to_string(&non_device_key_aux_data_json_sr25519.clone()) 298 | .unwrap() 299 | .into_bytes(), 300 | ); 301 | assert_eq!( 302 | DeviceKeyProxy::evaluate( 303 | request_from_non_device_key.clone(), 304 | Some(config_bytes.clone()), 305 | None 306 | ) 307 | .unwrap_err() 308 | .to_string(), 309 | "Error::InvalidSignatureRequest(\"Sr25519 Public key not in config\")" 310 | ); 311 | 312 | //ed25519 fail 313 | let ed25519_device_key_signature: Ed25519Signature = 314 | non_device_keys.ed25519_keys[0].sign(message.as_bytes()); 315 | let device_key_aux_data_json_ed25519 = AuxData { 316 | public_key_type: "ed25519".to_string(), 317 | public_key: BASE64_STANDARD.encode(non_device_keys.ed25519_keys[0].verifying_key()), 318 | signature: BASE64_STANDARD.encode(ed25519_device_key_signature.to_bytes()), 319 | context: "".to_string(), 320 | }; 321 | request_from_non_device_key.auxilary_data = Some( 322 | serde_json::to_string(&device_key_aux_data_json_ed25519) 323 | .unwrap() 324 | .into_bytes(), 325 | ); 326 | assert_eq!( 327 | DeviceKeyProxy::evaluate(request_from_non_device_key, Some(config_bytes), None) 328 | .unwrap_err() 329 | .to_string(), 330 | "Error::InvalidSignatureRequest(\"Ed25519 Public key not in config\")" 331 | ); 332 | } 333 | #[test] 334 | fn test_fails_with_no_aux_or_config() { 335 | let device_keys = generate_test_keys(); 336 | 337 | let config = UserConfig { 338 | ecdsa_public_keys: Some( 339 | device_keys 340 | .ecdsa_keys 341 | .iter() 342 | .map(|key| { 343 | let public_key = EcdsaPublicKey::from(key); 344 | let encoded_key = 345 | BASE64_STANDARD.encode(public_key.to_encoded_point(true).as_bytes()); 346 | encoded_key 347 | }) 348 | .collect(), 349 | ), 350 | sr25519_public_keys: None, 351 | ed25519_public_keys: None, 352 | }; 353 | let config_bytes = serde_json::to_vec(&config).unwrap(); 354 | 355 | let message = "this is some message that we want to sign if its from a valid device key"; 356 | let _device_key_signature: EcdsaSignature = device_keys.ecdsa_keys[0] 357 | .try_sign(message.as_bytes()) 358 | .unwrap(); 359 | 360 | let request_from_device_key_no_aux = SignatureRequest { 361 | message: message.to_string().into_bytes(), 362 | auxilary_data: None, 363 | }; 364 | 365 | assert_eq!( 366 | DeviceKeyProxy::evaluate( 367 | request_from_device_key_no_aux.clone(), 368 | Some(config_bytes.clone()), 369 | None 370 | ) 371 | .unwrap_err() 372 | .to_string(), 373 | "Error::InvalidSignatureRequest(\"No auxilary_data provided\")" 374 | ); 375 | 376 | let ecdsa_device_key_signature: EcdsaSignature = device_keys.ecdsa_keys[0] 377 | .try_sign(message.as_bytes()) 378 | .unwrap(); 379 | 380 | let mut device_key_aux_data_json = AuxData { 381 | public_key_type: "ecdsa".to_string(), 382 | public_key: BASE64_STANDARD.encode( 383 | device_keys.ecdsa_keys[0] 384 | .verifying_key() 385 | .to_encoded_point(true) 386 | .as_bytes(), 387 | ), 388 | signature: BASE64_STANDARD.encode(ecdsa_device_key_signature.to_bytes()), 389 | context: "".to_string(), 390 | }; 391 | let mut request_from_device_key = SignatureRequest { 392 | message: message.to_string().into_bytes(), 393 | auxilary_data: Some( 394 | serde_json::to_string(&device_key_aux_data_json) 395 | .unwrap() 396 | .into_bytes(), 397 | ), 398 | }; 399 | assert_eq!( 400 | DeviceKeyProxy::evaluate(request_from_device_key.clone(), None, None) 401 | .unwrap_err() 402 | .to_string(), 403 | "Error::Evaluation(\"No config provided.\")" 404 | ); 405 | 406 | device_key_aux_data_json.public_key_type = "phish".to_string(); 407 | request_from_device_key.auxilary_data = Some( 408 | serde_json::to_string(&device_key_aux_data_json) 409 | .unwrap() 410 | .into_bytes(), 411 | ); 412 | assert_eq!( 413 | DeviceKeyProxy::evaluate(request_from_device_key, Some(config_bytes.clone()), None) 414 | .unwrap_err() 415 | .to_string(), 416 | "Error::InvalidSignatureRequest(\"Invalid public key type\")" 417 | ); 418 | } 419 | 420 | /// Generates keys that can be used for testing 421 | fn generate_test_keys() -> TestKeys { 422 | let ecdsa_keys: Vec = (0..3).map(|_| EcdsaKeypair::random(&mut OsRng)).collect(); 423 | let sr25519_keys: Vec = (0..3) 424 | .map(|_| Sr25519Keypair::generate_with(&mut OsRng)) 425 | .collect(); 426 | let ed25519_keys: Vec = (0..3) 427 | .map(|_| Ed25519Keypair::generate(&mut OsRng)) 428 | .collect(); 429 | 430 | TestKeys { 431 | ecdsa_keys, 432 | sr25519_keys, 433 | ed25519_keys, 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /examples/infinite-loop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "infinite-loop" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/constraints" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | # This is required to compile programs to a wasm module and for use in rust libs 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | entropy-programs-core = { workspace = true } 18 | schemars = {version = "0.8.16", optional = true} 19 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 20 | 21 | # These are used by `cargo component` 22 | [package.metadata.component] 23 | package = "entropy:infinite-loop" 24 | 25 | [package.metadata.component.target] 26 | path = "../../wit" 27 | 28 | [package.metadata.component.dependencies] 29 | 30 | [features] 31 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/infinite-loop/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | use alloc::vec::Vec; 5 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 10 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 11 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 12 | pub struct UserConfig {} 13 | 14 | /// JSON representation of the auxiliary data 15 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 16 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 17 | pub struct AuxData {} 18 | 19 | // TODO confirm this isn't an issue for audit 20 | register_custom_getrandom!(always_fail); 21 | 22 | pub struct InfiniteLoop; 23 | 24 | impl Program for InfiniteLoop { 25 | /// This is the only function required by the program runtime. `message` is the preimage of the curve element to be 26 | /// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc. 27 | fn evaluate( 28 | _signature_request: SignatureRequest, 29 | _config: Option>, 30 | _oracle_data: Option>>, 31 | ) -> Result<(), Error> { 32 | loop {} 33 | #[allow(unreachable_code)] 34 | Ok(()) 35 | } 36 | 37 | /// Since we don't use a custom hash function, we can just return `None` here. 38 | fn custom_hash(_data: Vec) -> Option> { 39 | None 40 | } 41 | } 42 | 43 | export_program!(InfiniteLoop); 44 | -------------------------------------------------------------------------------- /examples/oracle-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oracle-example" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | 11 | # This is required to compile programs to a wasm module and for use in rust libs 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [dependencies] 16 | entropy-programs-core = { workspace = true } 17 | schemars = {version = "0.8.16", optional = true} 18 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 19 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } 20 | # These are used by `cargo component` 21 | [package.metadata.component] 22 | package = "entropy:oracle-example" 23 | 24 | [package.metadata.component.target] 25 | path = "../../wit" 26 | 27 | [package.metadata.component.dependencies] 28 | 29 | 30 | [features] 31 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/oracle-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to write a contrieved and basic program: checking the length of the data to be signed. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{ 8 | string::{String, ToString}, 9 | vec, 10 | vec::Vec, 11 | }; 12 | use codec::{Decode, Encode}; 13 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 14 | use serde::{Deserialize, Serialize}; 15 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 16 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 17 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 18 | pub struct UserConfig {} 19 | 20 | /// JSON representation of the auxiliary data 21 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 22 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 23 | pub struct AuxData {} 24 | 25 | // Oracle data is heading block_number_entropy as I expect that to be passed below to the evaluate function. 26 | pub const ORACLE_DATA: [&str; 1] = ["block_number_entropy"]; 27 | 28 | // TODO confirm this isn't an issue for audit 29 | register_custom_getrandom!(always_fail); 30 | 31 | pub struct OracleExample; 32 | 33 | impl Program for OracleExample { 34 | fn evaluate( 35 | _signature_request: SignatureRequest, 36 | _config: Option>, 37 | oracle_data: Option>>, 38 | ) -> Result<(), Error> { 39 | let data = oracle_data.ok_or(Error::Evaluation("No oracle data provided.".to_string()))?; 40 | let block_number = u32::decode(&mut data[0].as_ref()) 41 | .map_err(|_| Error::Evaluation("Unable to decode oracle data".to_string()))?; 42 | // our program just checks that the block number is greater than 100 43 | if block_number > 100 { 44 | return Err(Error::Evaluation("Block Number too large".to_string())); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | /// Since we don't use a custom hash function, we can just return `None` here. 51 | fn custom_hash(_data: Vec) -> Option> { 52 | None 53 | } 54 | } 55 | 56 | export_program!(OracleExample); 57 | 58 | // write a test that calls evaluate and passes it the proper parameters 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | use alloc::vec; 63 | use codec::Encode; 64 | 65 | #[test] 66 | fn test_should_sign() { 67 | let signature_request = SignatureRequest { 68 | message: "".to_string().into_bytes(), 69 | auxilary_data: None, 70 | }; 71 | 72 | assert!( 73 | OracleExample::evaluate(signature_request, None, Some(vec![99u32.encode()])).is_ok() 74 | ); 75 | } 76 | 77 | #[test] 78 | fn test_should_error() { 79 | let signature_request = SignatureRequest { 80 | message: "".to_string().into_bytes(), 81 | auxilary_data: None, 82 | }; 83 | 84 | assert_eq!( 85 | OracleExample::evaluate(signature_request, None, Some(vec![101u32.encode()])) 86 | .unwrap_err() 87 | .to_string(), 88 | "Error::Evaluation(\"Block Number too large\")" 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/private-acl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-private-acl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # This is required to compile programs to a wasm module and for use in rust libs 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | blake2 = "0.10.6" 12 | entropy-programs = { workspace = true } 13 | schemars = {version = "0.8.16", optional = true} 14 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 15 | 16 | [build-dependencies] 17 | hex = "0.4.3" 18 | blake2 = "0.10.6" 19 | 20 | # These are used by `cargo component` 21 | [package.metadata.component] 22 | package = "entropy:example-private-acl" 23 | 24 | [package.metadata.component.target] 25 | path = "../../wit" 26 | 27 | [package.metadata.component.dependencies] 28 | 29 | [features] 30 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/private-acl/addresses.txt: -------------------------------------------------------------------------------- 1 | 2 | 772b9a9e8aa1c9db861c6611a82d251db4fac990 3 | 772b9a9e8aa1c9db861c6611a82d251db4fac9aa 4 | -------------------------------------------------------------------------------- /examples/private-acl/build.rs: -------------------------------------------------------------------------------- 1 | //! This reads a text file of hex-encoded addresses, one per line, 2 | //! hashes them, and puts them in the constant ADDRESSES 3 | use blake2::{Blake2s256, Digest}; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::BufWriter; 7 | use std::io::Write; 8 | use std::io::{BufRead, BufReader}; 9 | use std::path::Path; 10 | 11 | fn main() { 12 | let out_dir = env::var_os("OUT_DIR").unwrap(); 13 | let dest_path = Path::new(&out_dir).join("addresses.rs"); 14 | let out_file = File::create(dest_path).unwrap(); 15 | let mut writer = BufWriter::new(out_file); 16 | 17 | // First count the number of non-empty lines in the file 18 | let length = { 19 | let file = File::open(format!("addresses.txt")).unwrap(); 20 | let reader = BufReader::new(file); 21 | let mut number_lines = 0; 22 | for line in reader.lines() { 23 | if !line.unwrap().is_empty() { 24 | number_lines += 1; 25 | } 26 | } 27 | number_lines 28 | }; 29 | 30 | let file = File::open(format!("addresses.txt")).unwrap(); 31 | let reader = BufReader::new(file); 32 | writer 33 | .write(format!("const ADDRESSES: [[u8; 32]; {}] = [", length).as_bytes()) 34 | .unwrap(); 35 | 36 | for line in reader.lines() { 37 | let line = line.unwrap(); 38 | if line.is_empty() { 39 | continue; 40 | } 41 | let address: [u8; 20] = hex::decode(line).unwrap().try_into().unwrap(); 42 | 43 | let hashed_address: [u8; 32] = { 44 | let mut hasher = Blake2s256::new(); 45 | hasher.update(&address); 46 | hasher.finalize().into() 47 | }; 48 | 49 | writer 50 | .write(format!(" {:?},", hashed_address).as_bytes()) 51 | .unwrap(); 52 | } 53 | writer.write("];".as_bytes()).unwrap(); 54 | 55 | println!("cargo:rerun-if-changed=build.rs"); 56 | println!("cargo:rerun-if-changed=addresses.txt"); 57 | } 58 | -------------------------------------------------------------------------------- /examples/private-acl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is an allow list where the allowed addresses are hashed to improve privacy 2 | //! It is still possible for anyone to check whether a given address is in the list, using the 3 | //! on-chain bytecode. But you cannot just read the allowed addresses from it. 4 | #![no_std] 5 | 6 | extern crate alloc; 7 | 8 | use alloc::string::ToString; 9 | use alloc::vec::Vec; 10 | use blake2::{Blake2s256, Digest}; 11 | use entropy_programs::{ 12 | arch::evm::NameOrAddress, 13 | core::{bindgen::*, export_program, prelude::*, TryParse}, 14 | programs::acl::*, 15 | }; 16 | use serde::{Deserialize, Serialize}; 17 | 18 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 19 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 20 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 21 | pub struct UserConfig {} 22 | 23 | /// JSON representation of the auxiliary data 24 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 25 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 26 | pub struct AuxData {} 27 | 28 | pub struct PrivateTransactionAcl; 29 | 30 | include!(concat!(env!("OUT_DIR"), "/addresses.rs")); 31 | 32 | // TODO confirm this isn't an issue for audit 33 | register_custom_getrandom!(always_fail); 34 | 35 | impl Program for PrivateTransactionAcl { 36 | /// Allow any address given in the pre-defined list (addresses.txt) 37 | // #[no_mangle] 38 | fn evaluate( 39 | signature_request: SignatureRequest, 40 | _config: Option>, 41 | _oracle_data: Option>>, 42 | ) -> Result<(), CoreError> { 43 | // parse the raw tx into some type 44 | let parsed_tx = ::TransactionRequest::try_parse( 45 | signature_request.message.as_slice(), 46 | )?; 47 | 48 | let name_or_address: NameOrAddress = parsed_tx.to.ok_or(Error::Evaluation( 49 | "No recipient given in transaction".to_string(), 50 | ))?; 51 | 52 | match name_or_address { 53 | NameOrAddress::Name(_) => Err(Error::Evaluation("ENS names not supported".to_string())), 54 | NameOrAddress::Address(address) => { 55 | let hashed_address = { 56 | let mut hasher = Blake2s256::new(); 57 | hasher.update(&address.0); 58 | hasher.finalize().into() 59 | }; 60 | if ADDRESSES.contains(&hashed_address) { 61 | Ok(()) 62 | } else { 63 | Err(Error::Evaluation("Address not in allow list".to_string())) 64 | } 65 | } 66 | } 67 | } 68 | 69 | fn custom_hash(_data: Vec) -> Option> { 70 | None 71 | } 72 | } 73 | 74 | export_program!(PrivateTransactionAcl); 75 | 76 | // write a test that calls evaluate and passes it the proper parameters 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use alloc::string::ToString; 81 | 82 | #[test] 83 | fn test_evaluate() { 84 | let signature_request = SignatureRequest { 85 | // `data` is an RLP serialized ETH transaction with the recipient set to `0x772b9a9e8aa1c9db861c6611a82d251db4fac990` 86 | message: "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080".to_string().into_bytes(), 87 | auxilary_data: None, 88 | }; 89 | 90 | assert!(PrivateTransactionAcl::evaluate(signature_request, None, None).is_ok()); 91 | } 92 | 93 | #[test] 94 | fn test_start_fail() { 95 | let signature_request = SignatureRequest { 96 | // `data` is the same as previous test, but recipient address ends in `1` instead of `0`, so it should fail 97 | message: "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac991019243726561746564204f6e20456e74726f7079018080".to_string().into_bytes(), 98 | auxilary_data: None, 99 | }; 100 | 101 | assert!(PrivateTransactionAcl::evaluate(signature_request, None, None).is_err()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-risc0" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # This is required to compile programs to a wasm module and for use in rust libs 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [dependencies] 15 | entropy-programs-core = { workspace = true } 16 | serde = { version = "1.0", default-features = false, features = ["derive"] } 17 | bincode = "1.3.3" 18 | # json-example = { path = "json" } 19 | risc0-zkvm = { workspace = true } 20 | schemars = {version = "0.8.16", optional = true} 21 | 22 | [dev-dependencies] 23 | json-core = { path = "json/core" } 24 | json-methods = { path = "json/methods" } 25 | risc0-zkvm = { git = "https://github.com/risc0/risc0", tag = "v0.18.0", default-features = true } 26 | 27 | [features] 28 | std = ["risc0-zkvm/std", "schemars"] 29 | 30 | # These are used by `cargo component` 31 | [package.metadata.component] 32 | package = "entropy:example-risc0" 33 | 34 | [package.metadata.component.target] 35 | path = "../../wit" 36 | 37 | [package.metadata.component.dependencies] 38 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-example" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/entropyxyz/constraints" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | json-core = { path = "core" } 12 | json-methods = { path = "methods" } 13 | risc0-zkvm = { git = "https://github.com/risc0/risc0", tag = "v0.18.0", default-features = false } 14 | serde = "1.0" 15 | 16 | [features] 17 | cuda = ["risc0-zkvm/cuda"] 18 | default = [] 19 | metal = ["risc0-zkvm/metal"] 20 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/README.md: -------------------------------------------------------------------------------- 1 | # JSON Example 2 | 3 | This code demonstrates how to prove that a JSON file contains a specific field and value using the RISC Zero zkVM. The JSON file is identified by SHA-256 hash, allowing users to commit to a specific JSON file and then prove some of its contents without revealing the full file. 4 | 5 | ## Quick Start 6 | 7 | First, [install Rust] if you don't already have it. 8 | 9 | Next, install the `cargo-risczero` tool and install the toolchain with: 10 | ```bash 11 | cargo install cargo-risczero 12 | cargo risczero install 13 | ``` 14 | 15 | Then, run the example with: 16 | ```bash 17 | cargo run --release 18 | ``` 19 | 20 | [install Rust]: https://doc.rust-lang.org/cargo/getting-started/installation.html 21 | 22 | ## Video Tutorial 23 | 24 | For a walk-through of this example, check out this [excerpt from our workshop at ZK HACK III](https://www.youtube.com/watch?v=6vIgBHx61vc&list=PLcPzhUaCxlCgig7ofeARMPwQ8vbuD6hC5&index=7). 25 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-core" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | risc0-zkvm = { git = "https://github.com/risc0/risc0", tag = "v0.18.0", default-features = false } 12 | serde = "1.0" 13 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use risc0_zkvm::sha::Digest; 16 | use serde::{Deserialize, Serialize}; 17 | 18 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 19 | pub struct Outputs { 20 | pub data: u32, 21 | pub hash: Digest, 22 | } 23 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-methods" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | [build-dependencies] 11 | risc0-build = { workspace = true } 12 | 13 | [package.metadata.risc0] 14 | methods = ["guest"] 15 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | fn main() { 16 | risc0_build::embed_methods(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/guest/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.69" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 28 | 29 | [[package]] 30 | name = "blake2" 31 | version = "0.10.6" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 34 | dependencies = [ 35 | "digest", 36 | ] 37 | 38 | [[package]] 39 | name = "block-buffer" 40 | version = "0.10.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 43 | dependencies = [ 44 | "generic-array", 45 | ] 46 | 47 | [[package]] 48 | name = "bytemuck" 49 | version = "1.13.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" 52 | dependencies = [ 53 | "bytemuck_derive", 54 | ] 55 | 56 | [[package]] 57 | name = "bytemuck_derive" 58 | version = "1.4.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" 61 | dependencies = [ 62 | "proc-macro2", 63 | "quote", 64 | "syn 1.0.107", 65 | ] 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "const-oid" 75 | version = "0.9.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" 78 | 79 | [[package]] 80 | name = "cpufeatures" 81 | version = "0.2.5" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 84 | dependencies = [ 85 | "libc", 86 | ] 87 | 88 | [[package]] 89 | name = "crypto-common" 90 | version = "0.1.6" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 93 | dependencies = [ 94 | "generic-array", 95 | "typenum", 96 | ] 97 | 98 | [[package]] 99 | name = "digest" 100 | version = "0.10.6" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 103 | dependencies = [ 104 | "block-buffer", 105 | "const-oid", 106 | "crypto-common", 107 | "subtle", 108 | ] 109 | 110 | [[package]] 111 | name = "elf" 112 | version = "0.7.2" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "e2b183d6ce6ca4cf30e3db37abf5b52568b5f9015c97d9fbdd7026aa5dcdd758" 115 | 116 | [[package]] 117 | name = "errno" 118 | version = "0.3.5" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" 121 | dependencies = [ 122 | "libc", 123 | "windows-sys", 124 | ] 125 | 126 | [[package]] 127 | name = "fastrand" 128 | version = "2.0.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 131 | 132 | [[package]] 133 | name = "generic-array" 134 | version = "0.14.6" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 137 | dependencies = [ 138 | "typenum", 139 | "version_check", 140 | ] 141 | 142 | [[package]] 143 | name = "getrandom" 144 | version = "0.2.8" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 147 | dependencies = [ 148 | "cfg-if", 149 | "libc", 150 | "wasi", 151 | ] 152 | 153 | [[package]] 154 | name = "hex" 155 | version = "0.4.3" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 158 | 159 | [[package]] 160 | name = "json" 161 | version = "0.12.4" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 164 | 165 | [[package]] 166 | name = "json-core" 167 | version = "0.1.0" 168 | dependencies = [ 169 | "risc0-zkvm", 170 | "serde", 171 | ] 172 | 173 | [[package]] 174 | name = "libc" 175 | version = "0.2.147" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 178 | 179 | [[package]] 180 | name = "libm" 181 | version = "0.2.6" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" 184 | 185 | [[package]] 186 | name = "linux-raw-sys" 187 | version = "0.4.10" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" 190 | 191 | [[package]] 192 | name = "log" 193 | version = "0.4.17" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 196 | dependencies = [ 197 | "cfg-if", 198 | ] 199 | 200 | [[package]] 201 | name = "num-derive" 202 | version = "0.4.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" 205 | dependencies = [ 206 | "proc-macro2", 207 | "quote", 208 | "syn 2.0.26", 209 | ] 210 | 211 | [[package]] 212 | name = "num-traits" 213 | version = "0.2.15" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 216 | dependencies = [ 217 | "autocfg", 218 | ] 219 | 220 | [[package]] 221 | name = "paste" 222 | version = "1.0.11" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" 225 | 226 | [[package]] 227 | name = "pin-project-lite" 228 | version = "0.2.9" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 231 | 232 | [[package]] 233 | name = "proc-macro2" 234 | version = "1.0.63" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 237 | dependencies = [ 238 | "unicode-ident", 239 | ] 240 | 241 | [[package]] 242 | name = "quote" 243 | version = "1.0.29" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 246 | dependencies = [ 247 | "proc-macro2", 248 | ] 249 | 250 | [[package]] 251 | name = "rand_core" 252 | version = "0.6.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 255 | 256 | [[package]] 257 | name = "redox_syscall" 258 | version = "0.3.5" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 261 | dependencies = [ 262 | "bitflags 1.3.2", 263 | ] 264 | 265 | [[package]] 266 | name = "risc0-binfmt" 267 | version = "0.18.0" 268 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 269 | dependencies = [ 270 | "anyhow", 271 | "elf", 272 | "log", 273 | "risc0-zkp", 274 | "risc0-zkvm-platform", 275 | "serde", 276 | ] 277 | 278 | [[package]] 279 | name = "risc0-circuit-rv32im" 280 | version = "0.18.0" 281 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 282 | dependencies = [ 283 | "anyhow", 284 | "log", 285 | "risc0-core", 286 | "risc0-zkp", 287 | "risc0-zkvm-platform", 288 | "tracing", 289 | ] 290 | 291 | [[package]] 292 | name = "risc0-core" 293 | version = "0.18.0" 294 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 295 | dependencies = [ 296 | "bytemuck", 297 | "rand_core", 298 | ] 299 | 300 | [[package]] 301 | name = "risc0-zkp" 302 | version = "0.18.0" 303 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 304 | dependencies = [ 305 | "anyhow", 306 | "blake2", 307 | "bytemuck", 308 | "digest", 309 | "hex", 310 | "log", 311 | "paste", 312 | "rand_core", 313 | "risc0-core", 314 | "risc0-zkvm-platform", 315 | "serde", 316 | "sha2", 317 | "tracing", 318 | ] 319 | 320 | [[package]] 321 | name = "risc0-zkvm" 322 | version = "0.18.0" 323 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 324 | dependencies = [ 325 | "anyhow", 326 | "bytemuck", 327 | "cfg-if", 328 | "getrandom", 329 | "hex", 330 | "libm", 331 | "log", 332 | "num-derive", 333 | "num-traits", 334 | "risc0-binfmt", 335 | "risc0-circuit-rv32im", 336 | "risc0-core", 337 | "risc0-zkp", 338 | "risc0-zkvm-platform", 339 | "serde", 340 | "tempfile", 341 | "tracing", 342 | ] 343 | 344 | [[package]] 345 | name = "risc0-zkvm-platform" 346 | version = "0.18.0" 347 | source = "git+https://github.com/risc0/risc0?tag=v0.18.0#29cc16f84e1f5f2af34120528b1f18a33e00ce71" 348 | 349 | [[package]] 350 | name = "rustix" 351 | version = "0.38.13" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" 354 | dependencies = [ 355 | "bitflags 2.4.0", 356 | "errno", 357 | "libc", 358 | "linux-raw-sys", 359 | "windows-sys", 360 | ] 361 | 362 | [[package]] 363 | name = "search_json" 364 | version = "0.12.0" 365 | dependencies = [ 366 | "json", 367 | "json-core", 368 | "risc0-zkvm", 369 | ] 370 | 371 | [[package]] 372 | name = "serde" 373 | version = "1.0.171" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" 376 | dependencies = [ 377 | "serde_derive", 378 | ] 379 | 380 | [[package]] 381 | name = "serde_derive" 382 | version = "1.0.171" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" 385 | dependencies = [ 386 | "proc-macro2", 387 | "quote", 388 | "syn 2.0.26", 389 | ] 390 | 391 | [[package]] 392 | name = "sha2" 393 | version = "0.10.6" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 396 | dependencies = [ 397 | "cfg-if", 398 | "cpufeatures", 399 | "digest", 400 | ] 401 | 402 | [[package]] 403 | name = "subtle" 404 | version = "2.4.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 407 | 408 | [[package]] 409 | name = "syn" 410 | version = "1.0.107" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 413 | dependencies = [ 414 | "proc-macro2", 415 | "quote", 416 | "unicode-ident", 417 | ] 418 | 419 | [[package]] 420 | name = "syn" 421 | version = "2.0.26" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" 424 | dependencies = [ 425 | "proc-macro2", 426 | "quote", 427 | "unicode-ident", 428 | ] 429 | 430 | [[package]] 431 | name = "tempfile" 432 | version = "3.8.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 435 | dependencies = [ 436 | "cfg-if", 437 | "fastrand", 438 | "redox_syscall", 439 | "rustix", 440 | "windows-sys", 441 | ] 442 | 443 | [[package]] 444 | name = "tracing" 445 | version = "0.1.37" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 448 | dependencies = [ 449 | "cfg-if", 450 | "pin-project-lite", 451 | "tracing-attributes", 452 | "tracing-core", 453 | ] 454 | 455 | [[package]] 456 | name = "tracing-attributes" 457 | version = "0.1.23" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "syn 1.0.107", 464 | ] 465 | 466 | [[package]] 467 | name = "tracing-core" 468 | version = "0.1.30" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 471 | 472 | [[package]] 473 | name = "typenum" 474 | version = "1.16.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 477 | 478 | [[package]] 479 | name = "unicode-ident" 480 | version = "1.0.6" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 483 | 484 | [[package]] 485 | name = "version_check" 486 | version = "0.9.4" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 489 | 490 | [[package]] 491 | name = "wasi" 492 | version = "0.11.0+wasi-snapshot-preview1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 495 | 496 | [[package]] 497 | name = "windows-sys" 498 | version = "0.48.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 501 | dependencies = [ 502 | "windows-targets", 503 | ] 504 | 505 | [[package]] 506 | name = "windows-targets" 507 | version = "0.48.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 510 | dependencies = [ 511 | "windows_aarch64_gnullvm", 512 | "windows_aarch64_msvc", 513 | "windows_i686_gnu", 514 | "windows_i686_msvc", 515 | "windows_x86_64_gnu", 516 | "windows_x86_64_gnullvm", 517 | "windows_x86_64_msvc", 518 | ] 519 | 520 | [[package]] 521 | name = "windows_aarch64_gnullvm" 522 | version = "0.48.5" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 525 | 526 | [[package]] 527 | name = "windows_aarch64_msvc" 528 | version = "0.48.5" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 531 | 532 | [[package]] 533 | name = "windows_i686_gnu" 534 | version = "0.48.5" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 537 | 538 | [[package]] 539 | name = "windows_i686_msvc" 540 | version = "0.48.5" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 543 | 544 | [[package]] 545 | name = "windows_x86_64_gnu" 546 | version = "0.48.5" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 549 | 550 | [[package]] 551 | name = "windows_x86_64_gnullvm" 552 | version = "0.48.5" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 555 | 556 | [[package]] 557 | name = "windows_x86_64_msvc" 558 | version = "0.48.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 561 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "search_json" 3 | version = "0.12.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | [workspace] 11 | 12 | [dependencies] 13 | json = "0.12" 14 | json-core = { path = "../../core" } 15 | risc0-zkvm = { git = "https://github.com/risc0/risc0", tag = "v0.18.0", default-features = false, features = [ 16 | "std", 17 | ] } 18 | 19 | 20 | # TODO copy the json project into the programs example and then resolve the deps via crates registry or 21 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/guest/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![no_main] 16 | 17 | use json::parse; 18 | use json_core::Outputs; 19 | use risc0_zkvm::{ 20 | guest::env, 21 | sha::{Impl, Sha256}, 22 | }; 23 | 24 | risc0_zkvm::guest::entry!(main); 25 | 26 | pub fn main() { 27 | let data: String = env::read(); 28 | let sha = *Impl::hash_bytes(&data.as_bytes()); 29 | let data = parse(&data).unwrap(); 30 | let proven_val = data["critical_data"].as_u32().unwrap(); 31 | let out = Outputs { 32 | data: proven_val, 33 | hash: sha, 34 | }; 35 | env::commit(&out); 36 | } 37 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | include!(concat!(env!("OUT_DIR"), "/methods.rs")); 16 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/res/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "boolean_field": true, 3 | "critical_data": 47, 4 | "obj_field": { 5 | "string_subfield": "hello world", 6 | "array_subfield": [ 7 | "more", 8 | "example", 9 | "text" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/json/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use json_core::Outputs; 16 | use json_methods::{SEARCH_JSON_ELF, SEARCH_JSON_ID}; 17 | use risc0_zkvm::{ 18 | default_prover, 19 | serde::{from_slice, to_vec}, 20 | ExecutorEnv, 21 | }; 22 | 23 | fn main() { 24 | let data = include_str!("../res/example.json"); 25 | let outputs = search_json(data); 26 | println!(); 27 | println!(" {:?}", outputs.hash); 28 | println!( 29 | "provably contains a field 'critical_data' with value {}", 30 | outputs.data 31 | ); 32 | } 33 | 34 | fn search_json(data: &str) -> Outputs { 35 | let env = ExecutorEnv::builder() 36 | .add_input(&to_vec(&data).unwrap()) 37 | .build() 38 | .unwrap(); 39 | 40 | // Obtain the default prover. 41 | let prover = default_prover(); 42 | 43 | // Produce a receipt by proving the specified ELF binary. 44 | let receipt = prover.prove_elf(env, SEARCH_JSON_ELF).unwrap(); 45 | 46 | // Intentionally change the ID (commitment) to something else. 47 | let mut different_id = SEARCH_JSON_ID.clone(); 48 | different_id[0] = 0x00; 49 | 50 | let _verified = receipt.verify(different_id); 51 | let _ = dbg!(_verified); 52 | 53 | from_slice(&receipt.journal).unwrap() 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | #[test] 59 | fn main() { 60 | let data = include_str!("../res/example.json"); 61 | let outputs = super::search_json(data); 62 | assert_eq!( 63 | outputs.data, 47, 64 | "Did not find the expected value in the critical_data field" 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to write a contrieved and basic program: checking the length of the data to be signed. 2 | 3 | #![cfg_attr(not(test), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{string::ToString, vec::Vec}; 8 | 9 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 10 | 11 | use bincode; 12 | use risc0_zkvm::Receipt; 13 | 14 | // TODO confirm this isn't an issue for audit 15 | register_custom_getrandom!(always_fail); 16 | 17 | pub struct ZkVmVerificationProgram; 18 | 19 | impl Program for ZkVmVerificationProgram { 20 | fn evaluate(signature_request: SignatureRequest, _config: Option>, _oracle_data: Option>>) -> Result<(), Error> { 21 | let image_id: [u32; 8] = bincode::deserialize(&signature_request.message) 22 | .map_err(|_| Error::InvalidSignatureRequest("Could not parse image_id".to_string()))?; 23 | 24 | let receipt: Receipt = match signature_request.auxilary_data { 25 | Some(serialized_receipt) => { 26 | bincode::deserialize(&serialized_receipt).map_err(|_| { 27 | Error::InvalidSignatureRequest("Could not parse receipt".to_string()) 28 | })? 29 | } 30 | None => { 31 | return Err(Error::InvalidSignatureRequest( 32 | "No receipt provided".to_string(), 33 | )) 34 | } 35 | }; 36 | 37 | receipt 38 | .verify(image_id) 39 | .map_err(|_| Error::Evaluation("Proof verification failed".to_string()))?; 40 | 41 | Ok(()) 42 | } 43 | 44 | /// Since we don't use a custom hash function, we can just return `None` here. 45 | fn custom_hash(_data: Vec) -> Option> { 46 | None 47 | } 48 | } 49 | 50 | export_program!(ZkVmVerificationProgram); 51 | 52 | // write a test that calls evaluate and passes it the proper parameters 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | use std::fs::{create_dir_all, File}; 58 | use std::io::{Read, Write}; 59 | use std::path::Path; 60 | 61 | use json_methods::{SEARCH_JSON_ELF, SEARCH_JSON_ID}; 62 | use risc0_zkvm::{default_prover, serde::to_vec, ExecutorEnv}; 63 | 64 | use helpers::*; 65 | 66 | #[ignore = "this test is only used to reproducibly generate test data"] 67 | #[test] 68 | fn generate_and_save_receipt_for_test() { 69 | let json_data = read_json_data(); 70 | 71 | // Generate receipt 72 | let receipt = generate_receipt(&json_data); 73 | 74 | // Write receipt to file 75 | let receipt_path = Path::new("test_data").join("zkvm_receipt.bin"); 76 | write_struct_to_file(&receipt_path, &receipt); 77 | 78 | // Write commitment to file 79 | let commitment_path = Path::new("test_data").join("zkvm_image_id.bin"); 80 | write_struct_to_file(&commitment_path, &SEARCH_JSON_ID); 81 | 82 | // Write an erroneous commitment to file 83 | let wrong_commitment = create_erroneous_commitment(); 84 | let wrong_commitment_path = Path::new("test_data").join("zkvm_wrong_image_id.bin"); 85 | write_struct_to_file(&wrong_commitment_path, &wrong_commitment); 86 | } 87 | 88 | #[test] 89 | fn test_should_pass_valid_receipt_and_image_pair() { 90 | let signature_request = SignatureRequest { 91 | message: bincode::serialize(&read_test_image_id()).unwrap(), 92 | auxilary_data: Some(bincode::serialize(&read_test_receipt()).unwrap()), 93 | }; 94 | 95 | assert!(ZkVmVerificationProgram::evaluate(signature_request, None, None).is_ok()); 96 | } 97 | 98 | #[test] 99 | fn test_should_error_with_incorrect_image_id_for_receipt_image_pair() { 100 | let signature_request = SignatureRequest { 101 | message: bincode::serialize(&read_erronous_test_image_id()).unwrap(), 102 | auxilary_data: Some(bincode::serialize(&read_test_receipt()).unwrap()), 103 | }; 104 | 105 | assert!(ZkVmVerificationProgram::evaluate(signature_request, None, None).is_err()); 106 | } 107 | 108 | // Test helper functions 109 | mod helpers { 110 | use super::*; 111 | 112 | /// Read a file from disk and deserialize it using bincode 113 | pub fn read_struct_from_file(filename: &str) -> T 114 | where 115 | T: serde::de::DeserializeOwned, 116 | { 117 | let dest_path = std::path::Path::new("test_data").join(filename); 118 | 119 | // Read the serialized data from disk 120 | let mut f = File::open(dest_path).expect("Failed to open file"); 121 | let mut serialized = Vec::new(); 122 | f.read_to_end(&mut serialized).expect("Failed to read data"); 123 | 124 | // Deserialize using bincode 125 | bincode::deserialize(&serialized).expect("Failed to deserialize data") 126 | } 127 | 128 | /// Read the test Receipt from disk 129 | pub fn read_test_receipt() -> Receipt { 130 | read_struct_from_file("zkvm_receipt.bin") 131 | } 132 | 133 | /// Read the test image ID from disk 134 | pub fn read_test_image_id() -> [u32; 8] { 135 | read_struct_from_file("zkvm_image_id.bin") 136 | } 137 | 138 | // Read zkvm_wrong_image_id.bin from disk 139 | pub fn read_erronous_test_image_id() -> [u32; 8] { 140 | read_struct_from_file("zkvm_wrong_image_id.bin") 141 | } 142 | 143 | pub fn read_json_data() -> String { 144 | include_str!("../json/res/example.json").to_string() 145 | } 146 | 147 | pub fn generate_receipt(json_data: &str) -> Receipt { 148 | let env = ExecutorEnv::builder() 149 | .add_input(&to_vec(&json_data).unwrap()) 150 | .build() 151 | .unwrap(); 152 | let prover = default_prover(); 153 | prover.prove_elf(env, SEARCH_JSON_ELF).unwrap() 154 | } 155 | 156 | pub fn write_struct_to_file(path: &Path, data: &T) { 157 | create_dir_all(&path.parent().unwrap()).expect("Failed to create directory"); 158 | let serialized_data = bincode::serialize(data).expect("Failed to serialize data"); 159 | let mut file = File::create(path).expect("Failed to create file"); 160 | file.write_all(&serialized_data) 161 | .expect("Failed to write data"); 162 | } 163 | 164 | pub fn create_erroneous_commitment() -> [u32; 8] { 165 | let mut wrong_commitment = SEARCH_JSON_ID.clone(); 166 | wrong_commitment[0] = 0x00; 167 | wrong_commitment 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/test_data/zkvm_image_id.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entropyxyz/programs/c351230c7eac77b7b9a86610cb88f4d724882ff7/examples/risczero-zkvm-verification/test_data/zkvm_image_id.bin -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/test_data/zkvm_receipt.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entropyxyz/programs/c351230c7eac77b7b9a86610cb88f4d724882ff7/examples/risczero-zkvm-verification/test_data/zkvm_receipt.bin -------------------------------------------------------------------------------- /examples/risczero-zkvm-verification/test_data/zkvm_wrong_image_id.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entropyxyz/programs/c351230c7eac77b7b9a86610cb88f4d724882ff7/examples/risczero-zkvm-verification/test_data/zkvm_wrong_image_id.bin -------------------------------------------------------------------------------- /examples/siwe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-siwe" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "Unlicense" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # This is required to compile programs to a wasm module and for use in rust libs 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [dependencies] 15 | entropy-programs-core = { workspace = true } 16 | siwe = "0.6.0" 17 | schemars = {version = "0.8.16", optional = true} 18 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 19 | 20 | # These are used by `cargo component` 21 | [package.metadata.component] 22 | package = "entropy:template-siwe" 23 | 24 | [package.metadata.component.target] 25 | path = "../../wit" 26 | 27 | [package.metadata.component.dependencies] 28 | 29 | 30 | [features] 31 | std = ["schemars"] -------------------------------------------------------------------------------- /examples/siwe/README.md: -------------------------------------------------------------------------------- 1 | # 'Sign-in with Ethereum' example template 2 | 3 | A template program which allows only valid Sign-in with Ethereum messages (EIP 4361) 4 | which sign-in to a given service. 5 | 6 | This could be used to share an account for a specific service whilst not allowing transactions 7 | to be signed. 8 | -------------------------------------------------------------------------------- /examples/siwe/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A template program which allows only valid Sign-in with Ethereum messages (EIP 4361) 2 | //! which sign-in to a given service. 3 | //! 4 | //! This could be used to share an account for a specific service whilst not allowing transactions 5 | //! to be signed. 6 | #![no_std] 7 | 8 | extern crate alloc; 9 | 10 | use alloc::{ 11 | string::{String, ToString}, 12 | vec::Vec, 13 | }; 14 | 15 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 16 | use serde::{Deserialize, Serialize}; 17 | use siwe::Message; 18 | 19 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 20 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 21 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 22 | pub struct UserConfig {} 23 | 24 | /// JSON representation of the auxiliary data 25 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 26 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 27 | pub struct AuxData {} 28 | 29 | // TODO confirm this isn't an issue for audit 30 | register_custom_getrandom!(always_fail); 31 | 32 | // The domain we allow a user to sign-in to. Change this to the desired service 33 | const ALLOWED_DOMAIN: &str = "localhost"; 34 | 35 | pub struct Siwe; 36 | 37 | impl Program for Siwe { 38 | fn evaluate( 39 | signature_request: SignatureRequest, 40 | _config: Option>, 41 | _oracle_data: Option>>, 42 | ) -> Result<(), Error> { 43 | let string_message = String::from_utf8(signature_request.message) 44 | .map_err(|err| Error::Evaluation(err.to_string()))?; 45 | let siwe_message = string_message 46 | .parse::() 47 | .map_err(|err| Error::Evaluation(err.to_string()))?; 48 | 49 | if siwe_message.domain == ALLOWED_DOMAIN { 50 | Ok(()) 51 | } else { 52 | Err(Error::Evaluation( 53 | "You may not sign-in to this domain".to_string(), 54 | )) 55 | } 56 | } 57 | 58 | /// Since we don't use a custom hash function, we can just return `None` here. 59 | fn custom_hash(_data: Vec) -> Option> { 60 | None 61 | } 62 | } 63 | 64 | export_program!(Siwe); 65 | 66 | // write a test that calls evaluate and passes it the proper parameters 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_should_sign() { 73 | let signature_request = SignatureRequest { 74 | message: "localhost wants you to sign in with your Ethereum account: 75 | 0x6Ee9894c677EFa1c56392e5E7533DE76004C8D94 76 | 77 | This is a test statement. 78 | 79 | URI: https://localhost/login 80 | Version: 1 81 | Chain ID: 1 82 | Nonce: oNCEHm5jzQU2WvuBB 83 | Issued At: 2022-01-28T23:28:16.013Z" 84 | .to_string() 85 | .into_bytes(), 86 | auxilary_data: None, 87 | }; 88 | 89 | assert!(Siwe::evaluate(signature_request, None, None).is_ok()); 90 | } 91 | 92 | #[test] 93 | fn test_bad_siwe_message() { 94 | let signature_request = SignatureRequest { 95 | message: "localhost does not want you to sign in with your Ethereum account: 96 | 0x6Ee9894c677EFa1c56392e5E7533DE76004C8D94 97 | 98 | This is a test statement. 99 | 100 | URI: https://localhost/login 101 | Version: 1 102 | Chain ID: 1 103 | Nonce: oNCEHm5jzQU2WvuBB 104 | Issued At: 2022-01-28T23:28:16.013Z" 105 | .to_string() 106 | .into_bytes(), 107 | auxilary_data: None, 108 | }; 109 | 110 | assert!(Siwe::evaluate(signature_request, None, None).is_err()); 111 | } 112 | 113 | #[test] 114 | fn test_bad_domain() { 115 | let signature_request = SignatureRequest { 116 | message: "google.com does not want you to sign in with your Ethereum account: 117 | 0x6Ee9894c677EFa1c56392e5E7533DE76004C8D94 118 | 119 | This is a test statement. 120 | 121 | URI: https://google.com/login 122 | Version: 1 123 | Chain ID: 1 124 | Nonce: oNCEHm5jzQU2WvuBB 125 | Issued At: 2022-01-28T23:28:16.013Z" 126 | .to_string() 127 | .into_bytes(), 128 | auxilary_data: None, 129 | }; 130 | 131 | assert!(Siwe::evaluate(signature_request, None, None).is_err()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /programs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entropy-programs" 3 | version = "0.1.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "AGPL-3.0-or-later" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | 10 | # [lib] 11 | # crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | entropy-programs-acl = { path = "../acl", default-features = false, features = ["evm"] } 15 | entropy-programs-evm = { path = "../evm", default-features = false } 16 | entropy-programs-core = { path = "../core", default-features = false } 17 | 18 | [dev-dependencies] 19 | # entropy-programs-runtime = { path = "../runtime", default-features = false } 20 | 21 | [features] 22 | default = ["std"] 23 | std = ["entropy-programs-acl/std", "entropy-programs-evm/std"] 24 | -------------------------------------------------------------------------------- /programs/README.md: -------------------------------------------------------------------------------- 1 | # `entropy-programs` 2 | 3 | This is the main library for writing programs. Currently, it is boilerplate and will re-export all programs and architectures. 4 | -------------------------------------------------------------------------------- /programs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Includes types and interfaces that are foundational to the core of programs. 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | pub use entropy_programs_core as core; 8 | /// All architecture-agnostic programs should be re-exported from this module 9 | pub mod programs { 10 | pub use entropy_programs_acl as acl; 11 | } 12 | /// All architectures that implement the `ec_core::Architecture` trait should be re-exported from here. 13 | pub mod arch { 14 | pub use entropy_programs_evm as evm; 15 | } 16 | 17 | /// Dynamic parsing allows for easily hooking transactions into 18 | pub mod parsing {} 19 | -------------------------------------------------------------------------------- /runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entropy-programs-runtime" 3 | version = "0.11.0" 4 | authors = ["Entropy Cryptography "] 5 | homepage = "https://entropy.xyz/" 6 | license = "AGPL-3.0-or-later" 7 | repository = "https://github.com/entropyxyz/programs" 8 | edition = "2021" 9 | description = "Webassembly runtime for running Entropy programs" 10 | 11 | [dependencies] 12 | wasmtime = { version = "12.0.1", features = ["component-model"] } 13 | entropy-programs-core = { version = "0.11.0", path = "../core" } 14 | thiserror = "1.0.47" 15 | 16 | [dev-dependencies] 17 | blake3 = "1.5.0" 18 | -------------------------------------------------------------------------------- /runtime/README.md: -------------------------------------------------------------------------------- 1 | # `entropy-programs-runtime` 2 | 3 | This contains the Wasm runtime for evaluaing, testing, and simulating programs as *Wasm Components*. 4 | 5 | ## Running Tests 6 | 7 | Before running the tests, you need to build the `template-barebones` and `infinite-loop` components. Be sure to have `cargo component` installed, and run `cargo component build --release -p template-barebones -p infinite-loop --target wasm32-unknown-unknown`. This will create the files needed for testing at `target/wasm32-unknown-unknown/release/`. 8 | -------------------------------------------------------------------------------- /runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Contains the Wasm runtime and related types for evaluating programs. 2 | 3 | use thiserror::Error; 4 | use wasmtime::{ 5 | component::{bindgen, Component, Linker}, 6 | Config as WasmtimeConfig, Engine, Result, Store, 7 | }; 8 | 9 | /// Note, this is wasmtime's bindgen, not wit-bindgen (modules) 10 | mod bindgen { 11 | use super::bindgen; 12 | 13 | bindgen!({ 14 | world: "program", 15 | }); 16 | } 17 | pub use bindgen::{Error as ProgramError, Program, SignatureRequest}; 18 | 19 | /// Runtime `Error` type 20 | #[derive(Debug, Error)] 21 | pub enum RuntimeError { 22 | /// Program bytecode is of zero length (core-side runtime error; Programs should probably not return this) 23 | #[error("Bytecode length is zero")] 24 | EmptyBytecode, 25 | /// Program bytecode is not a valid WebAssembly component. 26 | #[error("Invalid bytecode")] 27 | InvalidBytecode, 28 | /// Program error during execution. 29 | #[error("Runtime error: {0}")] 30 | Runtime(ProgramError), 31 | /// Program exceeded fuel limits. Execute fewer instructions. 32 | #[error("Out of fuel")] 33 | OutOfFuel, 34 | } 35 | 36 | /// Config is for runtime parameters (eg instructions per program, additional runtime interfaces, etc). 37 | pub struct Config { 38 | /// Max number of instructions the runtime will execute before returning an error. 39 | pub fuel: u64, 40 | } 41 | 42 | impl Default for Config { 43 | fn default() -> Self { 44 | Self { fuel: 10_000 } 45 | } 46 | } 47 | 48 | /// Runtime allows for the execution of programs. Instantiate with `Runtime::new()`. 49 | pub struct Runtime { 50 | engine: Engine, 51 | linker: Linker<()>, 52 | store: Store<()>, 53 | } 54 | 55 | impl Default for Runtime { 56 | fn default() -> Self { 57 | Self::new(Config::default()) 58 | } 59 | } 60 | 61 | impl Runtime { 62 | pub fn new(config: Config) -> Self { 63 | let mut wasmtime_config = WasmtimeConfig::new(); 64 | wasmtime_config 65 | .wasm_component_model(true) 66 | .consume_fuel(true); 67 | 68 | let engine = Engine::new(&wasmtime_config).unwrap(); 69 | let linker = Linker::new(&engine); 70 | let mut store = Store::new(&engine, ()); 71 | 72 | store.add_fuel(config.fuel).unwrap(); 73 | Self { 74 | engine, 75 | linker, 76 | store, 77 | } 78 | } 79 | } 80 | 81 | impl Runtime { 82 | /// Evaluate a program with a given initial state. 83 | pub fn evaluate( 84 | &mut self, 85 | program: &[u8], 86 | signature_request: &SignatureRequest, 87 | config: Option<&[u8]>, 88 | oracle_data: Option<&[Vec]>, 89 | ) -> Result<(), RuntimeError> { 90 | if program.len() == 0 { 91 | return Err(RuntimeError::EmptyBytecode); 92 | } 93 | 94 | let component = Component::from_binary(&self.engine, program) 95 | .map_err(|_| RuntimeError::InvalidBytecode)?; 96 | let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker) 97 | .map_err(|_| RuntimeError::InvalidBytecode)?; 98 | 99 | bindings 100 | .call_evaluate(&mut self.store, signature_request, config, oracle_data) 101 | .map_err(|_| RuntimeError::OutOfFuel)? 102 | .map_err(RuntimeError::Runtime) 103 | } 104 | 105 | /// Compute the `custom-hash` of a `message` from the program. 106 | pub fn custom_hash( 107 | &mut self, 108 | program: &[u8], 109 | message: &[u8], 110 | ) -> Result<[u8; 32], RuntimeError> { 111 | if program.len() == 0 { 112 | return Err(RuntimeError::EmptyBytecode); 113 | } 114 | 115 | let component = Component::from_binary(&self.engine, program) 116 | .map_err(|_| RuntimeError::InvalidBytecode)?; 117 | let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker) 118 | .map_err(|_| RuntimeError::InvalidBytecode)?; 119 | 120 | let hash_as_vec = bindings 121 | .call_custom_hash(&mut self.store, message) 122 | .unwrap().ok_or(RuntimeError::Runtime(ProgramError::InvalidSignatureRequest("`custom-hash` returns `None`. Implement the hash function in your program, or select a predefined `hash` in your signature request.".to_string())))?; 123 | if hash_as_vec.len() != 32 { 124 | return Err(RuntimeError::Runtime( 125 | ProgramError::InvalidSignatureRequest(format!( 126 | "`custom-hash` must returns a Vec of length 32, not {}.", 127 | hash_as_vec.len() 128 | )), 129 | )); 130 | } 131 | 132 | let mut hash = [0u8; 32]; 133 | hash.copy_from_slice(&hash_as_vec); 134 | Ok(hash) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /runtime/tests/runtime.rs: -------------------------------------------------------------------------------- 1 | /// Points to the `template-barebones` program binary. 2 | const BAREBONES_COMPONENT_WASM: &[u8] = 3 | include_bytes!("../../target/wasm32-unknown-unknown/release/template_barebones.wasm"); 4 | const CUSTOM_HASH_COMPONENT_WASM: &[u8] = 5 | include_bytes!("../../target/wasm32-unknown-unknown/release/example_custom_hash.wasm"); 6 | /// Points to the `infinite-loop` program binary. 7 | const INFINITE_LOOP_WASM: &[u8] = 8 | include_bytes!("../../target/wasm32-unknown-unknown/release/infinite_loop.wasm"); 9 | 10 | use blake3; 11 | use entropy_programs_runtime::{Runtime, SignatureRequest}; 12 | 13 | #[test] 14 | fn test_barebones_component() { 15 | let mut runtime = Runtime::default(); 16 | 17 | // The barebones example simply validates that the length of the data to be signed is greater than 10. 18 | let longer_than_10 = "asdfasdfasdfasdf".to_string(); 19 | let signature_request = SignatureRequest { 20 | message: longer_than_10.into_bytes(), 21 | auxilary_data: None, 22 | }; 23 | 24 | let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request, None, None); 25 | assert!(res.is_ok()); 26 | } 27 | 28 | #[test] 29 | fn test_barebones_component_fails_with_data_length_less_than_10() { 30 | let mut runtime = Runtime::default(); 31 | 32 | // Since the barebones example verifies that the length of the data to be signed is greater than 10, this should fail. 33 | let shorter_than_10 = "asdf".to_string(); 34 | let signature_request = SignatureRequest { 35 | message: shorter_than_10.into_bytes(), 36 | auxilary_data: None, 37 | }; 38 | 39 | let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request, None, None); 40 | assert!(res.is_err()); 41 | } 42 | 43 | #[test] 44 | fn test_empty_bytecode_fails() { 45 | let mut runtime = Runtime::default(); 46 | 47 | let signature_request = SignatureRequest { 48 | message: vec![], 49 | auxilary_data: None, 50 | }; 51 | 52 | let res = runtime.evaluate(&[], &signature_request, None, None); 53 | assert_eq!(res.unwrap_err().to_string(), "Bytecode length is zero"); 54 | } 55 | 56 | #[test] 57 | fn test_infinite_loop() { 58 | let mut runtime = Runtime::default(); 59 | 60 | let signature_request = SignatureRequest { 61 | message: vec![], 62 | auxilary_data: None, 63 | }; 64 | 65 | let res = runtime.evaluate(INFINITE_LOOP_WASM, &signature_request, None, None); 66 | assert_eq!(res.unwrap_err().to_string(), "Out of fuel"); 67 | } 68 | 69 | #[test] 70 | fn test_custom_hash() { 71 | let mut runtime = Runtime::default(); 72 | 73 | let message = "some_data_to_be_hashed".to_string().into_bytes(); 74 | 75 | let mut expected_hash = [0u8; 32]; 76 | let expected_hash_as_vec = blake3::hash(&message).as_bytes().to_vec(); 77 | expected_hash.copy_from_slice(&expected_hash_as_vec); 78 | 79 | let actual_hash = runtime 80 | .custom_hash(CUSTOM_HASH_COMPONENT_WASM, message.as_slice()) 81 | .unwrap(); 82 | 83 | assert_eq!(actual_hash, expected_hash); 84 | } 85 | 86 | #[test] 87 | fn test_custom_hash_errors_when_returning_none() { 88 | let mut runtime = Runtime::default(); 89 | 90 | let message = "some_data_to_be_hashed".to_string().into_bytes(); 91 | 92 | let res = runtime.custom_hash( 93 | // Remember, barebones component doesn't define a custom hash function 94 | BAREBONES_COMPONENT_WASM, 95 | message.as_slice(), 96 | ); 97 | assert!(res.is_err()); 98 | assert_eq!( 99 | res.unwrap_err().to_string(), 100 | "Runtime error: Error::InvalidSignatureRequest(\"`custom-hash` returns `None`. Implement the hash function in your program, or select a predefined `hash` in your signature request.\")" 101 | ); 102 | } 103 | 104 | // TODO add test for custom hash returning a vec of length != 32 105 | -------------------------------------------------------------------------------- /runtime/wit: -------------------------------------------------------------------------------- 1 | ../wit -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "clippy"] 4 | targets = ["wasm32-unknown-unknown"] 5 | -------------------------------------------------------------------------------- /templates/basic-template/.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | .git 3 | -------------------------------------------------------------------------------- /templates/basic-template/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | {{project-name}}_serialized_config_type.txt 3 | {{project-name}}_serialized_aux_data_type.txt 4 | .env -------------------------------------------------------------------------------- /templates/basic-template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | authors = ["{{authors}}"] 5 | license = "Unlicense" 6 | edition = "2021" 7 | 8 | [workspace] 9 | members = ["generate-types", ".", "cli"] 10 | resolver = "2" 11 | 12 | # strip debug info since that makes up a major part of Wasm blobs, see Wasm's `twiggy` 13 | [profile.release] 14 | strip = "debuginfo" 15 | 16 | # This is required to compile programs to a wasm module and for use in rust libs 17 | [lib] 18 | crate-type = ["cdylib", "rlib"] 19 | 20 | [dependencies] 21 | entropy-programs-core = { git = "https://github.com/entropyxyz/programs.git", branch = "master" } 22 | schemars = { version = "0.8.16", optional = true } 23 | serde = { version = "1.0", default-features = false, features = [ 24 | "alloc", 25 | "derive", 26 | ] } 27 | 28 | # These are used by `cargo component` 29 | [package.metadata.component] 30 | package = "entropy:{{project-name}}" 31 | 32 | [package.metadata.component.dependencies] 33 | 34 | # Metadata related to an Entropy program 35 | [package.metadata.entropy-program] 36 | 37 | # The docker image used to build this program 38 | docker-image = "entropyxyz/build-entropy-programs:v0.0.1" 39 | 40 | # Configuration interface description 41 | # interface-description = "" 42 | 43 | [features] 44 | std = ["schemars"] 45 | -------------------------------------------------------------------------------- /templates/basic-template/Dockerfile: -------------------------------------------------------------------------------- 1 | # To build this program, and put the .wasm binary in the directory 'output': 2 | # docker build --output=binary-dir . 3 | FROM entropyxyz/build-entropy-programs:v0.0.1 AS base 4 | 5 | WORKDIR /usr/src/programs 6 | COPY . . 7 | 8 | RUN cargo component build --release --target wasm32-unknown-unknown 9 | 10 | FROM scratch AS binary 11 | COPY --from=base /usr/src/programs/target/wasm32-unknown-unknown/release/*.wasm / 12 | -------------------------------------------------------------------------------- /templates/basic-template/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # {{project-name}} 4 | 5 | ## Running tests 6 | 7 | `cargo test` 8 | 9 | ## Building the program 10 | 11 | Get the necessary build tools with: 12 | 13 | ```shell 14 | cargo install cargo-component --version 0.2.0 && 15 | cargo install wasm-tools 16 | ``` 17 | 18 | Then build with: 19 | 20 | ```shell 21 | cargo component build --release --target wasm32-unknown-unknown 22 | ``` 23 | 24 | The `.wasm` binary can be found in `./target/wasm32-unknown-unknown/release` 25 | 26 | ## Building with docker 27 | 28 | If you want to make your program publicly available and open source, and make it possible for others to verify that the source code corresponds to the on-chain binary, you can build it with the included Dockerfile: 29 | 30 | ```shell 31 | docker build --output=binary-dir . 32 | ``` 33 | 34 | This will compile your program and put the `.wasm` binary file in `./binary-dir`. 35 | 36 | ## Generate Types 37 | 38 | Types are meant top be posted with the program, it is how people know how to interact with your program 39 | 40 | They will be autogenerated when running store-programs, or you can run it manually 41 | 42 | ```shell 43 | cargo run -p generate-types 44 | ``` 45 | 46 | Will generate two files that will hold both the aux_data_schema and config_schema 47 | 48 | ## Upload program 49 | 50 | The basic template is shipped with a cli to upload a program, after compiling the program then generating the types 51 | you upload the program to chain. 52 | 53 | Create a .env file with two variables 54 | 55 | ```env 56 | DEPLOYER_MNEMONIC="" 57 | ENTROPY_DEVNET="" 58 | ``` 59 | 60 | Then run: 61 | 62 | ```shell 63 | cargo run -p cli -- store-program 64 | ``` 65 | -------------------------------------------------------------------------------- /templates/basic-template/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.20", features = ["derive"] } 8 | subxt = { version = "0.35.3", features = ["substrate-compat"] } 9 | colored = "2.0.4" 10 | tokio = { version = "1.16", features = [ 11 | "macros", 12 | "rt-multi-thread", 13 | "io-util", 14 | "process", 15 | ] } 16 | anyhow = "1.0.82" 17 | dotenv = "0.15.0" 18 | generate-types = { path = "../generate-types" } 19 | project-root = "0.2.2" 20 | entropy-test-cli = { git = "https://github.com/entropyxyz/entropy-core.git", branch = "master" } 21 | -------------------------------------------------------------------------------- /templates/basic-template/cli/entropy_metadata.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entropyxyz/programs/c351230c7eac77b7b9a86610cb88f4d724882ff7/templates/basic-template/cli/entropy_metadata.scale -------------------------------------------------------------------------------- /templates/basic-template/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use colored::Colorize; 3 | use dotenv::dotenv; 4 | use entropy_test_cli::{run_command, Cli, PROGRAM_VERSION_NUMBER}; 5 | use generate_types::generate_types; 6 | use project_root::get_project_root; 7 | use std::fs; 8 | 9 | #[tokio::main] 10 | async fn main() -> anyhow::Result<()> { 11 | dotenv().ok(); 12 | let program = format!( 13 | "{}/target/wasm32-unknown-unknown/release/{{project-name}}.wasm", 14 | get_project_root()?.to_string_lossy() 15 | ); 16 | generate_types(); 17 | let config_interface = format!( 18 | "{}/{{project-name}}_serialized_config_type.txt", 19 | get_project_root()?.to_string_lossy() 20 | ); 21 | let aux_data_interface = format!( 22 | "{}/{{project-name}}_serialized_aux_data_type.txt", 23 | get_project_root()?.to_string_lossy() 24 | ); 25 | 26 | let oracle_data = format!( 27 | "{}/{{project-name}}_serialized_oracle_data_type.txt", 28 | get_project_root()?.to_string_lossy() 29 | ); 30 | 31 | // length is 1 if empty and can ignore, scale codec length 32 | let decoded_oracle_data = fs::read(oracle_data.clone()).unwrap(); 33 | let oracle_option = if decoded_oracle_data.len() == 1 { 34 | None 35 | } else { 36 | Some(oracle_data.into()) 37 | }; 38 | 39 | let cli = Cli::parse(); 40 | let json_ouput = cli.json; 41 | match run_command( 42 | cli, 43 | Some(program.into()), 44 | Some(config_interface.into()), 45 | Some(aux_data_interface.into()), 46 | oracle_option, 47 | Some(PROGRAM_VERSION_NUMBER), 48 | ) 49 | .await 50 | { 51 | Ok(output) => { 52 | if json_ouput { 53 | println!("{}", output); 54 | } else { 55 | println!("Success: {}", output.green()); 56 | } 57 | Ok(()) 58 | } 59 | Err(err) => { 60 | if !json_ouput { 61 | eprintln!("{}", "Failed!".red()); 62 | } 63 | Err(err) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /templates/basic-template/generate-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generate-types" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | program = { package = "{{project-name}}", path = "..", default-features = false, features = [ 10 | 'std', 11 | ] } 12 | schemars = "0.8.16" 13 | serde_json = { version = "1.0" } 14 | codec = { package = "parity-scale-codec", version = "3.6.8", default-features = false } 15 | -------------------------------------------------------------------------------- /templates/basic-template/generate-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | use schemars::schema_for; 2 | use std::fs; 3 | use program::{UserConfig, AuxData, ORACLE_DATA}; 4 | use codec::Encode; 5 | 6 | pub fn generate_types() { 7 | let schema_config = schema_for!(UserConfig); 8 | fs::write( 9 | "./tests_serialized_config_type.txt", 10 | format!( 11 | "{:?}", 12 | serde_json::to_vec(&schema_config) 13 | .expect("error converting user config") 14 | ), 15 | ) 16 | .expect("Failed to write to config"); 17 | 18 | let schema_aux_data = schema_for!(AuxData); 19 | fs::write( 20 | "./tests_serialized_aux_data_type.txt", 21 | format!( 22 | "{:?}", 23 | serde_json::to_vec(&schema_aux_data) 24 | .expect("error converting user aux_data") 25 | ), 26 | ) 27 | .expect("Failed to write to proxy aux_data"); 28 | 29 | let oracle_data = ORACLE_DATA.iter().map(|x| x.encode()).collect::>(); 30 | fs::write( 31 | "./tests_serialized_oracle_data_type.txt", 32 | oracle_data.encode() 33 | ) 34 | .expect("Failed to write oracle_data"); 35 | } -------------------------------------------------------------------------------- /templates/basic-template/generate-types/src/main.rs: -------------------------------------------------------------------------------- 1 | use generate_types::generate_types; 2 | 3 | fn main() { 4 | generate_types(); 5 | } -------------------------------------------------------------------------------- /templates/basic-template/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! No-op program 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | extern crate alloc; 6 | 7 | use alloc::{string::ToString, vec::Vec}; 8 | 9 | use entropy_programs_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | // TODO confirm this isn't an issue for audit 16 | register_custom_getrandom!(always_fail); 17 | 18 | /// JSON-deserializable struct that will be used to derive the program-JSON interface. 19 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 20 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 21 | pub struct UserConfig { 22 | } 23 | 24 | /// JSON representation of the auxiliary data 25 | #[cfg_attr(feature = "std", derive(schemars::JsonSchema))] 26 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 27 | pub struct AuxData { 28 | } 29 | 30 | /// Oracle data used, (change 0 to amount of items in vector) 31 | pub const ORACLE_DATA: [&str; 0] = []; 32 | 33 | pub struct {{project-name | upper_camel_case}}; 34 | 35 | impl Program for {{project-name | upper_camel_case}} { 36 | fn evaluate( 37 | signature_request: SignatureRequest, 38 | _config: Option>, 39 | _oracle_data: Option>>, 40 | ) -> Result<(), Error> { 41 | if signature_request.message.is_empty() { 42 | return Err(Error::Evaluation( 43 | "Message must have a length greater than zero".to_string(), 44 | )); 45 | } 46 | Ok(()) 47 | } 48 | 49 | /// Since we don't use a custom hash function, we can just return `None` here. 50 | fn custom_hash(_data: Vec) -> Option> { 51 | None 52 | } 53 | } 54 | 55 | export_program!({{project-name | upper_camel_case}}); 56 | -------------------------------------------------------------------------------- /templates/basic-template/src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_should_sign() { 5 | let signature_request = SignatureRequest { 6 | message: b"some_message".to_vec(), 7 | auxilary_data: None, 8 | }; 9 | 10 | assert!({{project-name | upper_camel_case}}::evaluate(signature_request, None, None).is_ok()); 11 | } 12 | 13 | #[test] 14 | fn test_should_fail() { 15 | let signature_request = SignatureRequest { 16 | message: Vec::new(), 17 | auxilary_data: None, 18 | }; 19 | 20 | assert!({{project-name | upper_camel_case}}::evaluate(signature_request, None, None).is_err()); 21 | } -------------------------------------------------------------------------------- /wit/application.wit: -------------------------------------------------------------------------------- 1 | package entropy:core 2 | 3 | world program { 4 | // similar to `variant`, but no type payloads 5 | variant error { 6 | /// The signature request is invalid (ie. the `initial-state` ) 7 | invalid-signature-request(string), 8 | evaluation(string) 9 | } 10 | /// Evaluates the program given the user's signature request and the program's configuration. 11 | export evaluate: func(signature-request: signature-request, config: option>, oracle-data: option>>) -> result<_, error> 12 | 13 | /// Programs that use custom hash functions can a custom 32-byte curve point to be signed. 14 | export custom-hash: func(data: list) -> option> 15 | 16 | record signature-request { 17 | /// Preimage of the user's data that will be signed (eg. RLP-encoded ETH transaction request). 18 | message: list, 19 | /// Auxiliary data optionally required for program evaluation; this won't be signed (eg. zero-knowledge proof, third party signature) 20 | auxilary-data: option> 21 | } 22 | } 23 | --------------------------------------------------------------------------------