├── .github ├── CODEOWNERS └── workflows │ ├── audit_new.yml │ ├── nostd.yml │ ├── tests.yml │ └── wasm.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── adss ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md └── src │ ├── lib.rs │ └── strobe_rng.rs ├── ppoprf ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── benches │ └── bench.rs ├── examples │ ├── client.rs │ └── server.rs └── src │ ├── ggm.rs │ ├── lib.rs │ ├── ppoprf.rs │ └── strobe_rng.rs ├── renovate.json ├── rustfmt.toml ├── sharks ├── .gitignore ├── CHANGELOG.md ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches │ └── benchmarks.rs ├── codecov.yml ├── fuzz │ ├── .gitignore │ ├── Cargo.toml │ └── fuzz_targets │ │ ├── deserialize_share.rs │ │ ├── generate_shares.rs │ │ ├── recover.rs │ │ └── serialize_share.rs └── src │ ├── lib.rs │ └── share_ff.rs ├── star-wasm ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── src │ └── lib.rs └── www │ ├── .eslintrc.js │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── bootstrap.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── server.js │ └── webpack.config.js └── star ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── benches └── bench.rs ├── src ├── lib.rs └── strobe_rng.rs ├── test-utils ├── Cargo.toml └── src │ └── lib.rs └── tests └── e2e.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @brave/star-reviewers 2 | -------------------------------------------------------------------------------- /.github/workflows/audit_new.yml: -------------------------------------------------------------------------------- 1 | name: Audit 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**/Cargo.toml' 7 | - '**/Cargo.lock' 8 | pull_request: 9 | branches: 10 | - main 11 | schedule: 12 | - cron: '12 13 2 * *' 13 | 14 | jobs: 15 | audit: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 20 | 21 | - name: Install cargo audit 22 | uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 23 | with: 24 | command: install 25 | args: --force cargo-audit 26 | 27 | - name: Audit 28 | run: cargo audit --deny warnings 29 | -------------------------------------------------------------------------------- /.github/workflows/nostd.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: Tests (nostd) 8 | 9 | jobs: 10 | test-nofeatures: 11 | name: ${{matrix.rust}} on ${{matrix.os}} 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | matrix: 16 | rust: [stable] 17 | os: [ubuntu-latest] 18 | 19 | env: 20 | RUSTFLAGS: '' 21 | CARGO_PROFILE_DEV_DEBUG: '0' # reduce size of target directory 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 26 | 27 | - name: Toolchain 28 | run: rustup default ${{matrix.rust}} 29 | 30 | - name: Check 31 | run: cargo check --no-default-features --all-targets 32 | 33 | - name: Test 34 | run: cargo test --release --no-default-features 35 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: Tests 8 | 9 | jobs: 10 | test: 11 | name: ${{matrix.rust}} on ${{matrix.os}} 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | matrix: 16 | rust: [stable] 17 | os: [ubuntu-latest] 18 | 19 | env: 20 | RUSTFLAGS: '' 21 | CARGO_PROFILE_DEV_DEBUG: '0' # reduce size of target directory 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 26 | 27 | - name: Toolchain 28 | run: rustup default ${{matrix.rust}} 29 | 30 | - name: Build 31 | uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 32 | with: 33 | command: build 34 | args: --release --all-targets 35 | 36 | - name: Test 37 | uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 38 | with: 39 | command: test 40 | args: --release 41 | -------------------------------------------------------------------------------- /.github/workflows/wasm.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: Tests 8 | 9 | jobs: 10 | wasm: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | RUSTFLAGS: '' 15 | CARGO_PROFILE_DEV_DEBUG: '0' # reduce size of target directory 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 20 | 21 | - name: WASM pkg 22 | run: | 23 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 24 | cd ./star-wasm 25 | make build 26 | 27 | - name: WASM www 28 | run: | 29 | cd ./star-wasm/www 30 | make 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.env 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brave/sta-rs/f93638fdcf5ca9e5548a199e4d2ee3dbf45bf590/.gitmodules -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "./adss", 4 | "./sharks", 5 | "./star", 6 | "./star-wasm", 7 | "./ppoprf", 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sta-rs 2 | 3 | Rust workspace for implementing basic functionality of [STAR: Distributed 4 | Secret-Sharing for Threshold Aggregation 5 | Reporting](https://arxiv.org/abs/2109.10074). 6 | 7 | ## Disclaimer 8 | 9 | WARNING the libraries present in this workspace have not been audited, 10 | use at your own risk! This code is under active development and may 11 | change substantially in future versions. 12 | 13 | ## Crates 14 | 15 | - [sta-rs](./star): A rust implementation of the [STAR 16 | protocol](https://arxiv.org/abs/2109.10074). 17 | - [ppoprf](./ppoprf): A rust implementation of the PPOPRF protocol 18 | detailed in the [STAR paper](https://arxiv.org/abs/2109.10074). 19 | - [sharks](./sharks): A fork of the existing [sharks 20 | crate](https://crates.io/crates/sharks) for performing Shamir secret 21 | sharing, using larger base fields of sizes 129 and 255 bits. The 22 | fields were implemented using 23 | - [adss](./adss): A rust implementation of the [Adept Secret 24 | Sharing scheme](https://eprint.iacr.org/2020/800) of Bellare et al, 25 | based on the forked [star-sharks](./sharks) crate, using the underlying 26 | finite field implementation made available in 27 | [zkcrypto/ff](https://github.com/zkcrypto/ff). 28 | - [star-wasm](./star-wasm): WASM bindings for using [star](./star) 29 | functionality. 30 | 31 | ## Quickstart 32 | 33 | Build & test: 34 | ``` 35 | cargo build 36 | cargo test 37 | ``` 38 | 39 | Benchmarks: 40 | ``` 41 | cargo bench 42 | ``` 43 | 44 | Open local copy of documentation: 45 | ``` 46 | cargo doc --open --no-deps 47 | ``` 48 | ## Example usage 49 | 50 | ### WASM 51 | 52 | See [star-wasm](./star-wasm/src/lib.rs) for public API functions exposed 53 | by libraries. 54 | 55 | - The `create_share` function should be called by clients, and creates 56 | the `share` and `tag` sent in a STAR client message, as well as the 57 | encryption `key` used to encrypt data to the server. Once this 58 | function has been called, use `key` to encrypt the desired data into a 59 | `ciphertext` object (using a valid AES encryption method). The client 60 | should then send `(ciphertext, share, tag)` to the aggregation server. 61 | - The `group_shares` function takes in a collection of `share` objects 62 | and recovers the `key` object that the client used for encrypting 63 | `ciphertext`. This function only succeeds if the number of shares is 64 | higher than the prescribed threshold. 65 | 66 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The code in this repository is still being actively developed, and is thus regarded as suitable only for experimental purposes. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any observed issues to the [GitHub repository](https://github.com/brave/sta-rs/). 10 | -------------------------------------------------------------------------------- /adss/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | 4 | # Added by cargo 5 | # 6 | # already existing elements were commented out 7 | 8 | #/target 9 | Cargo.lock 10 | -------------------------------------------------------------------------------- /adss/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## v0.2.2 (2023-09-14) 9 | 10 | - Avoid `Share::from_bytes` panic when serialized share is too small 11 | 12 | ## v0.2.1 (2023-07-19) 13 | 14 | - Update crate metadata 15 | Fill out Cargo.toml to meet crates.io publication requirements. 16 | - Vendor strobe-rng to allow publication. 17 | 18 | ### Commit Details 19 | 20 | 21 | 22 |
view details 23 | 24 | * **Uncategorized** 25 | - Merge pull request #275 from brave/adss-0.2.1 (f2300de) 26 | - Bump version number for adss-0.2.1 (0124edc) 27 | - Update crate metadata (8ad7543) 28 | - Rename adss-rs to plain adss (29fbd42) 29 |
30 | 31 | ## v0.2.0 (unreleased, 2023 March 31) 32 | 33 | - Clear key material after use (zeroize) 34 | - Documentation fixes 35 | - API cleanup 36 | - Update deps 37 | 38 | ## v0.1.3 (unreleased, circa 2021 September) 39 | 40 | - Initial version used with Web Discovery Project 41 | -------------------------------------------------------------------------------- /adss/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "adss" 3 | version = "0.2.3" 4 | authors = ["eV ", "Alex Davidson "] 5 | description = "Adept Secret Sharing framework" 6 | documentation = "https://docs.rs/adss" 7 | repository = "https://github.com/brave/sta-rs" 8 | keywords = ["crypto", "secret", "share"] 9 | categories = ["cryptography", "algorithms"] 10 | license = "MPL-2.0" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | strobe-rs = "0.10.0" 15 | rand = "0.8.5" 16 | rand_core = "0.6.4" 17 | zeroize = "1.5.5" 18 | 19 | [dependencies.star-sharks] 20 | default-features = false 21 | version = "0.6.0" 22 | path = "../sharks" 23 | features = ["zeroize_memory"] 24 | -------------------------------------------------------------------------------- /adss/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /adss/README.md: -------------------------------------------------------------------------------- 1 | # adss crate 2 | 3 | Rust implementation of the [Adept Secret Sharing framework](https://eprint.iacr.org/2020/800). 4 | 5 | ## Disclaimer 6 | 7 | WARNING the libraries present in this workspace have not been audited, 8 | use at your own risk! This code is under active development and may 9 | change substantially in future versions. 10 | 11 | See the [changelog](CHANGELOG.md) for information about different 12 | versions. 13 | 14 | ## Quickstart 15 | 16 | Build & test: 17 | ``` 18 | cargo build 19 | cargo test 20 | ``` 21 | 22 | Open a local copy of the documentation: 23 | ``` 24 | cargo doc --open --no-deps 25 | ``` 26 | -------------------------------------------------------------------------------- /adss/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The code in this repository is still being actively developed, and is thus regarded as suitable only for experimental purposes. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any observed issues to the [GitHub repository](https://github.com/brave/sta-rs/). 10 | -------------------------------------------------------------------------------- /adss/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `adss` crate defines functionality for performing secret 2 | //! sharing with established security guarantees. We use this framework 3 | //! as it allows for specifying the random coins that are used for 4 | //! establishing the lagrange polynomial coefficients explicitly. A 5 | //! description of the Adept Secret Sharing framework is provided in 6 | //! the paper by [Bellare et al.](https://eprint.iacr.org/2020/800). 7 | 8 | use star_sharks::Sharks; 9 | use std::convert::{TryFrom, TryInto}; 10 | use std::error::Error; 11 | use std::fmt; 12 | use strobe_rs::{SecParam, Strobe}; 13 | use zeroize::{Zeroize, ZeroizeOnDrop}; 14 | 15 | mod strobe_rng; 16 | use strobe_rng::StrobeRng; 17 | 18 | /// The length of a serialized `AccessStructure`, in bytes. 19 | pub const ACCESS_STRUCTURE_LENGTH: usize = 4; 20 | 21 | /// The length of a message authentication code used in `Share`, in bytes. 22 | pub const MAC_LENGTH: usize = 64; 23 | 24 | /// The `AccessStructure` struct defines the policy under which shares 25 | /// can be recovered. Currently, this policy is simply whether there are 26 | /// `threshold` number of independent shares. 27 | #[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] 28 | pub struct AccessStructure { 29 | threshold: u32, 30 | } 31 | 32 | /// Append a `u32` in little-endian coding 33 | pub fn store_u32(u: u32, out: &mut Vec) { 34 | out.extend(u.to_le_bytes()); 35 | } 36 | 37 | /// Attempt to parse a little-endian value from a byte serialization 38 | pub fn load_u32(bytes: &[u8]) -> Option { 39 | if bytes.len() != 4 { 40 | return None; 41 | } 42 | 43 | let mut bits: [u8; 4] = [0u8; 4]; 44 | bits.copy_from_slice(bytes); 45 | Some(u32::from_le_bytes(bits)) 46 | } 47 | 48 | /// Append a chunk of data 49 | /// 50 | /// Extends the output `Vec` with the passed slice, prepending 51 | /// a 4-byte, little-endian length header so it can be parsed 52 | /// out later. 53 | pub fn store_bytes(s: &[u8], out: &mut Vec) { 54 | store_u32(s.len() as u32, out); 55 | out.extend(s); 56 | } 57 | 58 | /// Parse the next data chunk out of a byte slice 59 | /// 60 | /// This reads a 4-byte, little-endian length header and 61 | /// then returns a new slice with the data bounded by 62 | /// that header. 63 | /// 64 | /// Returns `None` if there is insufficient data for 65 | /// the complete chunk. 66 | pub fn load_bytes(bytes: &[u8]) -> Option<&[u8]> { 67 | if bytes.len() < 4 { 68 | return None; 69 | } 70 | 71 | let len: u32 = load_u32(&bytes[..4])?; 72 | if bytes.len() < (4 + len) as usize { 73 | return None; 74 | } 75 | 76 | Some(&bytes[4..4 + len as usize]) 77 | } 78 | 79 | /// An `AccessStructure` defines how a message is to be split among multiple parties 80 | /// 81 | /// In particular this determines how many shares will be issued and what threshold of the shares 82 | /// are needed to reconstruct the original `Commune`. 83 | impl AccessStructure { 84 | /// Convert this `AccessStructure` to a byte array. 85 | pub fn to_bytes(&self) -> [u8; ACCESS_STRUCTURE_LENGTH] { 86 | self.threshold.to_le_bytes() 87 | } 88 | 89 | /// Parse a serialized `AccessStructure` from a byte slice. 90 | /// 91 | /// Returns `None` if a valid structure was not found, for 92 | /// example if the slice was too short. 93 | pub fn from_bytes(bytes: &[u8]) -> Option { 94 | let threshold = load_u32(bytes)?; 95 | Some(AccessStructure { threshold }) 96 | } 97 | } 98 | 99 | #[allow(non_snake_case)] 100 | impl From for Sharks { 101 | fn from(A: AccessStructure) -> Sharks { 102 | Sharks(A.threshold) 103 | } 104 | } 105 | 106 | /// A a unique instance of sharing across multiple parties 107 | /// 108 | /// A `Commune` consists of an access structure defining the 109 | /// parameters of the sharing, a secret message which will be shared, 110 | /// "random coins" which provide strong but possibly non-uniform 111 | /// entropy and an optional STROBE transcript which can include 112 | /// extra data which will be authenticated. 113 | #[allow(non_snake_case)] 114 | #[derive(Clone, ZeroizeOnDrop)] 115 | pub struct Commune { 116 | /// `A` is an `AccessStructure` defining the sharing 117 | A: AccessStructure, 118 | /// `M` is the message to be shared 119 | M: Vec, 120 | /// `R` are the "random coins" which may not be uniform 121 | R: Vec, 122 | /// `T` is a `Strobe` transcript which forms optional tags to be authenticated 123 | T: Option, 124 | } 125 | 126 | /// The `Share` struct holds the necessary data that is encoded in a 127 | /// single secret share. A share itself reveals nothing about the 128 | /// encoded secret data until it is grouped with a set of shares that 129 | /// satisfy the policy in the associated `AccessStructure`. 130 | #[allow(non_snake_case)] 131 | #[derive(Clone, Eq, PartialEq, Zeroize)] 132 | pub struct Share { 133 | A: AccessStructure, 134 | S: star_sharks::Share, 135 | /// C is the encrypted message 136 | C: Vec, 137 | /// D is the encrypted randomness 138 | D: Vec, 139 | /// J is a MAC showing knowledge of A, M, R, and T 140 | J: [u8; MAC_LENGTH], 141 | T: (), 142 | } 143 | 144 | impl fmt::Debug for Share { 145 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 146 | f.debug_struct("Share").field("S", &self.S).finish() 147 | } 148 | } 149 | 150 | impl Share { 151 | pub fn to_bytes(&self) -> Vec { 152 | let mut out: Vec = Vec::new(); 153 | 154 | // A: AccessStructure 155 | out.extend(self.A.to_bytes()); 156 | 157 | // S: star_sharks::Share 158 | store_bytes(&Vec::from(&self.S), &mut out); 159 | 160 | // C: Vec 161 | store_bytes(&self.C, &mut out); 162 | 163 | // D: Vec 164 | store_bytes(&self.D, &mut out); 165 | 166 | // J: [u8; 64] 167 | out.extend(self.J); 168 | 169 | out 170 | } 171 | 172 | pub fn from_bytes(bytes: &[u8]) -> Option { 173 | let mut slice = bytes; 174 | 175 | // A: AccessStructure 176 | let a = AccessStructure::from_bytes(&slice[..ACCESS_STRUCTURE_LENGTH])?; 177 | slice = &slice[ACCESS_STRUCTURE_LENGTH..]; 178 | 179 | // S: star_sharks::Share 180 | let sb = load_bytes(slice)?; 181 | slice = &slice[4 + sb.len()..]; 182 | 183 | // C: Vec 184 | let c = load_bytes(slice)?; 185 | slice = &slice[4 + c.len()..]; 186 | 187 | // D: Vec 188 | let d = load_bytes(slice)?; 189 | slice = &slice[4 + d.len()..]; 190 | 191 | // J: [u8; 64] 192 | let j: [u8; MAC_LENGTH] = slice.try_into().ok()?; 193 | 194 | Some(Share { 195 | A: a, 196 | S: star_sharks::Share::try_from(sb).ok()?, 197 | C: c.to_vec(), 198 | D: d.to_vec(), 199 | J: j, 200 | T: (), 201 | }) 202 | } 203 | } 204 | 205 | #[allow(non_snake_case)] 206 | impl Commune { 207 | pub fn new( 208 | threshold: u32, 209 | message: Vec, 210 | randomness: Vec, 211 | transcript: Option, 212 | ) -> Self { 213 | Commune { 214 | A: AccessStructure { threshold }, 215 | M: message, 216 | R: randomness, 217 | T: transcript, 218 | } 219 | } 220 | 221 | /// The `share` function samples a single secret share for the 222 | /// specified `message`. 223 | pub fn share(self) -> Result> { 224 | // H4κ = (A, M, R, T) 225 | let mut transcript = self 226 | .T 227 | .clone() 228 | .unwrap_or_else(|| Strobe::new(b"adss", SecParam::B128)); 229 | transcript.ad(&self.A.to_bytes(), false); 230 | transcript.ad(&self.M, false); 231 | transcript.key(&self.R, false); 232 | 233 | // J is a MAC which authenticates A, M, R, and T 234 | let mut J = [0u8; 64]; 235 | transcript.send_mac(&mut J, false); 236 | 237 | // K is the derived key used to encrypt the message and our "random coins" 238 | let mut K = [0u8; 16]; 239 | transcript.prf(&mut K, false); 240 | 241 | // L is the randomness to be fed to secret sharing polynomial generation 242 | let mut L: StrobeRng = transcript.into(); 243 | 244 | let mut key = Strobe::new(b"adss encrypt", SecParam::B128); 245 | key.key(&K, false); 246 | 247 | // C is the encrypted message 248 | let mut C: Vec = vec![0; self.M.len()]; 249 | C.copy_from_slice(&self.M); 250 | key.send_enc(&mut C, false); 251 | 252 | // D is the encrypted randomness 253 | let mut D: Vec = vec![0; self.R.len()]; 254 | D.copy_from_slice(&self.R); 255 | key.send_enc(&mut D, false); 256 | 257 | // Generate a random share 258 | let mut K_vec: Vec = K.to_vec(); 259 | K_vec.extend(vec![0u8; 16]); 260 | let polys = Sharks::from(self.A.clone()).dealer_rng(&K_vec, &mut L)?; 261 | let S = polys.gen(&mut rand::rngs::OsRng); 262 | Ok(Share { 263 | A: self.A.clone(), 264 | S, 265 | C, 266 | D, 267 | J, 268 | T: (), 269 | }) 270 | } 271 | 272 | pub fn get_message(&self) -> Vec { 273 | self.M.clone() 274 | } 275 | 276 | fn verify(&self, J: &[u8; MAC_LENGTH]) -> Result<(), Box> { 277 | let mut transcript = self 278 | .clone() 279 | .T 280 | .clone() 281 | .unwrap_or_else(|| Strobe::new(b"adss", SecParam::B128)); 282 | transcript.ad(&self.A.to_bytes(), false); 283 | transcript.ad(&self.M, false); 284 | transcript.key(&self.R, false); 285 | 286 | transcript 287 | .recv_mac(J) 288 | .map_err(|_| "Mac validation failed".into()) 289 | } 290 | } 291 | 292 | #[allow(non_snake_case)] 293 | /// The `recover` function attempts to recover a secret shared value 294 | /// from a set of shares that meet the threshold requirements. 295 | pub fn recover<'a, T>(shares: T) -> Result> 296 | where 297 | T: IntoIterator, 298 | T::IntoIter: Iterator, 299 | { 300 | let mut shares = shares.into_iter().peekable(); 301 | let s = &(*shares.peek().ok_or("no shares passed")?).clone(); 302 | let shares: Vec = shares.cloned().map(|s| s.S).collect(); 303 | let key = Sharks::from(s.A.clone()).recover(&shares)?; 304 | let K = key[..16].to_vec(); 305 | 306 | let mut key = Strobe::new(b"adss encrypt", SecParam::B128); 307 | key.key(&K, false); 308 | 309 | // M is the message 310 | let mut M: Vec = vec![0; s.C.len()]; 311 | M.copy_from_slice(&s.C); 312 | key.recv_enc(&mut M, false); 313 | 314 | // R are the "random coins" 315 | let mut R: Vec = vec![0; s.D.len()]; 316 | R.copy_from_slice(&s.D); 317 | key.recv_enc(&mut R, false); 318 | 319 | let c = Commune { 320 | A: s.A.clone(), 321 | M, 322 | R, 323 | T: None, 324 | }; 325 | 326 | c.verify(&s.J.clone())?; 327 | Ok(c) 328 | } 329 | 330 | #[cfg(test)] 331 | mod tests { 332 | use core::iter; 333 | 334 | use crate::*; 335 | 336 | #[test] 337 | fn serialization_u32() { 338 | for &i in &[0, 10, 100, u32::MAX] { 339 | let mut out: Vec = Vec::new(); 340 | store_u32(i, &mut out); 341 | assert_eq!(load_u32(out.as_slice()), Some(i)); 342 | } 343 | } 344 | 345 | #[test] 346 | fn serialization_empty_bytes() { 347 | let mut out: Vec = Vec::new(); 348 | store_bytes(Vec::new().as_slice(), &mut out); 349 | assert_eq!(load_bytes(out.as_slice()), Some(&[] as &[u8])); 350 | } 351 | 352 | #[test] 353 | fn serialization_bytes() { 354 | let mut out: Vec = Vec::new(); 355 | let bytes: &[u8] = &[0, 1, 10, 100]; 356 | store_bytes(bytes, &mut out); 357 | assert_eq!(load_bytes(out.as_slice()), Some(bytes)); 358 | } 359 | 360 | #[test] 361 | fn serialization_access_structure() { 362 | for i in &[0, 10, 100, u32::MAX] { 363 | let a = AccessStructure { threshold: *i }; 364 | let s: &[u8] = &a.to_bytes()[..]; 365 | assert_eq!(AccessStructure::from_bytes(s), Some(a)); 366 | } 367 | } 368 | 369 | #[test] 370 | fn serialization_share() { 371 | let c = Commune { 372 | A: AccessStructure { threshold: 50 }, 373 | M: vec![1, 2, 3, 4], 374 | R: vec![5, 6, 7, 8], 375 | T: None, 376 | }; 377 | 378 | for share in iter::repeat_with(|| c.clone().share()).take(150) { 379 | let share = share.unwrap(); 380 | let s = share.to_bytes(); 381 | assert_eq!(Share::from_bytes(s.as_slice()), Some(share)); 382 | 383 | // Test shares that are erroneously truncated 384 | assert_eq!(Share::from_bytes(&s[..s.len() - 7]), None); 385 | } 386 | } 387 | 388 | #[test] 389 | fn it_works() { 390 | let c = Commune { 391 | A: AccessStructure { threshold: 50 }, 392 | M: vec![1, 2, 3, 4], 393 | R: vec![5, 6, 7, 8], 394 | T: None, 395 | }; 396 | 397 | let shares: Vec = iter::repeat_with(|| c.clone().share().unwrap()) 398 | .take(150) 399 | .collect(); 400 | 401 | let recovered = recover(&shares).unwrap(); 402 | 403 | assert_eq!(c.M, recovered.M); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /adss/src/strobe_rng.rs: -------------------------------------------------------------------------------- 1 | /// RngCore impl for the Strobe hash 2 | /// 3 | /// FIXME: This should be submitted upstream as a feature-gated 4 | /// extension. Failing that, publish as a separate helper crate 5 | /// to avoid this code duplication. 6 | use rand_core::{CryptoRng, RngCore}; 7 | use strobe_rs::Strobe; 8 | 9 | /// StrobeRng implements the RngCore trait by using STROBE as an entropy pool 10 | pub struct StrobeRng { 11 | strobe: Strobe, 12 | } 13 | 14 | impl From for StrobeRng { 15 | fn from(strobe: Strobe) -> Self { 16 | StrobeRng { strobe } 17 | } 18 | } 19 | 20 | impl RngCore for StrobeRng { 21 | fn next_u32(&mut self) -> u32 { 22 | rand_core::impls::next_u32_via_fill(self) 23 | } 24 | 25 | fn next_u64(&mut self) -> u64 { 26 | rand_core::impls::next_u64_via_fill(self) 27 | } 28 | 29 | fn fill_bytes(&mut self, dest: &mut [u8]) { 30 | let dest_len = (dest.len() as u32).to_le_bytes(); 31 | self.strobe.meta_ad(&dest_len, false); 32 | self.strobe.prf(dest, false); 33 | } 34 | 35 | fn try_fill_bytes( 36 | &mut self, 37 | dest: &mut [u8], 38 | ) -> Result<(), rand_core::Error> { 39 | self.fill_bytes(dest); 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl CryptoRng for StrobeRng {} 45 | -------------------------------------------------------------------------------- /ppoprf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ppoprf" 3 | version = "0.4.2" 4 | authors = ["Alex Davidson ", "Ralph Ankele "] 5 | description = "Puncturable Partially-Oblivious Pseudo-Random Function" 6 | documentation = "https://docs.rs/ppoprf" 7 | repository = "https://github.com/brave/sta-rs" 8 | keywords = ["crypto", "oprf", "protocol"] 9 | categories = ["cryptography"] 10 | license = "MPL-2.0" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | rand = { version = "0.8.5", features = [ "getrandom" ] } 15 | bitvec = { version = "1.0.1", features = ["serde"] } 16 | curve25519-dalek = { version = "4.0.0", features = [ "rand_core", "serde" ] } 17 | serde = { version = "1.0.147", features = ["derive"] } 18 | strobe-rs = "0.10.0" 19 | base64 = "0.22" 20 | bincode = "1.3.3" 21 | derive_more = "0.99" 22 | zeroize = { version = "1.5.5", features = [ "derive" ] } 23 | rand_core = "0.6.4" 24 | 25 | [dev-dependencies] 26 | criterion = "0.5.1" 27 | env_logger = "0.11.3" 28 | log = "0.4.21" 29 | reqwest = { version = "0.12.4", features = [ "blocking", "json" ] } 30 | dotenvy = "0.15.7" 31 | tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "time"] } 32 | warp = "0.3.7" 33 | 34 | [[bench]] 35 | name = "bench" 36 | harness = false 37 | 38 | [features] 39 | key-sync = [] 40 | -------------------------------------------------------------------------------- /ppoprf/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /ppoprf/README.md: -------------------------------------------------------------------------------- 1 | # ppoprf 2 | 3 | An implementation of the Puncturable Partially Oblivious Pseudorandom Function designed in https://arxiv.org/abs/2109.10074. 4 | -------------------------------------------------------------------------------- /ppoprf/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The code in this repository is still being actively developed, and is thus regarded as suitable only for experimental purposes. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any observed issues to the [GitHub repository](https://github.com/brave-experiments/sta-rs/). -------------------------------------------------------------------------------- /ppoprf/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use curve25519_dalek::ristretto::RistrettoPoint; 4 | use rand::{rngs::OsRng, Rng}; 5 | 6 | use ppoprf::ggm::GGM; 7 | use ppoprf::ppoprf::{Client, Point, Server}; 8 | use ppoprf::PPRF; 9 | 10 | fn criterion_benchmark(c: &mut Criterion) { 11 | benchmark_ggm(c); 12 | benchmark_ppoprf(c); 13 | benchmark_server(c); 14 | benchmark_client(c); 15 | } 16 | 17 | fn benchmark_ggm(c: &mut Criterion) { 18 | c.bench_function("GGM setup", |b| { 19 | b.iter(GGM::setup); 20 | }); 21 | 22 | c.bench_function("GGM eval 1 bit", |b| { 23 | let ggm = GGM::setup(); 24 | let mut out = vec![0u8; 32]; 25 | let input = b"x"; 26 | b.iter(|| { 27 | ggm.eval(input, &mut out).unwrap(); 28 | }); 29 | }); 30 | 31 | c.bench_function("GGM setup & puncture 1 input", |b| { 32 | let input = b"x"; 33 | b.iter(|| { 34 | let mut ggm = GGM::setup(); 35 | ggm.puncture(input).unwrap(); 36 | }); 37 | }); 38 | 39 | c.bench_function("GGM setup & puncture all inputs", |b| { 40 | let inputs: Vec = (0..=255).collect(); 41 | b.iter(|| { 42 | let mut ggm = GGM::setup(); 43 | for &x in &inputs { 44 | ggm.puncture(&[x]).unwrap(); 45 | } 46 | }); 47 | }); 48 | } 49 | 50 | fn benchmark_ppoprf(c: &mut Criterion) { 51 | let mds = vec![0u8]; 52 | let server = Server::new(mds.clone()).unwrap(); 53 | let c_input = b"a_random_client_input"; 54 | c.bench_function("PPOPRF end-to-end evaluation", |b| { 55 | b.iter(|| { 56 | end_to_end_evaluation(&server, c_input, 0, false, &mut [0u8; 32]) 57 | }); 58 | }); 59 | 60 | c.bench_function("PPOPRF end-to-end evaluation verifiable", |b| { 61 | b.iter(|| end_to_end_evaluation(&server, c_input, 0, true, &mut [0u8; 32])); 62 | }); 63 | 64 | c.bench_function("PPOPRF setup & puncture 1 input", |b| { 65 | b.iter(|| { 66 | let mut server = Server::new(mds.clone()).unwrap(); 67 | server.puncture(mds[0]).unwrap(); 68 | }); 69 | }); 70 | 71 | c.bench_function("PPOPRF setup & puncture all inputs", |b| { 72 | let inputs: Vec = (0..=255).collect(); 73 | b.iter(|| { 74 | let mut server = Server::new(inputs.clone()).unwrap(); 75 | for &md in inputs.iter() { 76 | server.puncture(md).unwrap(); 77 | } 78 | }); 79 | }); 80 | } 81 | 82 | fn benchmark_server(c: &mut Criterion) { 83 | let mds: Vec = (0..=7).collect(); 84 | c.bench_function("Server setup", |b| { 85 | b.iter(|| { 86 | Server::new(mds.clone()).unwrap(); 87 | }) 88 | }); 89 | 90 | c.bench_function("Server puncture 1 input", |b| { 91 | b.iter(|| { 92 | let mut server = Server::new(mds.clone()).unwrap(); 93 | server.puncture(0u8).unwrap(); 94 | }) 95 | }); 96 | 97 | c.bench_function("Server eval", |b| { 98 | let server = Server::new(mds.clone()).unwrap(); 99 | let point = Point::from(RistrettoPoint::random(&mut OsRng)); 100 | b.iter(|| { 101 | server.eval(&point, 0, false).unwrap(); 102 | }) 103 | }); 104 | 105 | c.bench_function("Server verifiable eval", |b| { 106 | let server = Server::new(mds.clone()).unwrap(); 107 | let point = Point::from(RistrettoPoint::random(&mut OsRng)); 108 | b.iter(|| { 109 | server.eval(&point, 0, true).unwrap(); 110 | }) 111 | }); 112 | } 113 | 114 | fn benchmark_client(c: &mut Criterion) { 115 | let mds: Vec = (0..=7).collect(); 116 | let mut input = [0u8; 32]; 117 | OsRng.fill(&mut input); 118 | let server = Server::new(mds.clone()).unwrap(); 119 | 120 | c.bench_function("Client blind", |b| { 121 | b.iter(|| { 122 | Client::blind(input.as_ref()); 123 | }) 124 | }); 125 | 126 | c.bench_function("Client verify", |b| { 127 | let (blinded_point, _) = Client::blind(input.as_ref()); 128 | let eval = server.eval(&blinded_point, 0, true).unwrap(); 129 | b.iter(|| { 130 | Client::verify(&server.get_public_key(), &blinded_point, &eval, 0) 131 | }) 132 | }); 133 | 134 | c.bench_function("Client unblind", |b| { 135 | let (blinded_point, r) = Client::blind(input.as_ref()); 136 | b.iter(|| { 137 | Client::unblind(&blinded_point, &r); 138 | }) 139 | }); 140 | 141 | c.bench_function("Client finalize", |b| { 142 | let random_point = RistrettoPoint::random(&mut OsRng); 143 | b.iter(|| { 144 | Client::finalize( 145 | input.as_ref(), 146 | mds[0], 147 | &Point::from(random_point), 148 | &mut [0u8; 32], 149 | ); 150 | }) 151 | }); 152 | } 153 | 154 | // The `end_to_end_evaluation` helper function for performs a full 155 | // PPOPRF protocol evaluation. 156 | fn end_to_end_evaluation( 157 | server: &Server, 158 | input: &[u8], 159 | md: u8, 160 | verify: bool, 161 | out: &mut [u8], 162 | ) { 163 | let (blinded_point, r) = Client::blind(input); 164 | let evaluated = server.eval(&blinded_point, md, verify).unwrap(); 165 | if verify 166 | && !Client::verify(&server.get_public_key(), &blinded_point, &evaluated, md) 167 | { 168 | panic!("Verification failed") 169 | } 170 | let unblinded = Client::unblind(&evaluated.output, &r); 171 | Client::finalize(input, md, &unblinded, out); 172 | } 173 | 174 | criterion_group!(benches, criterion_benchmark); 175 | criterion_main!(benches); 176 | -------------------------------------------------------------------------------- /ppoprf/examples/client.rs: -------------------------------------------------------------------------------- 1 | //! Example clieant for the ppoprf randomness web service. 2 | //! 3 | //! This tests and demonstrates the ppoprf evaluation function 4 | //! in an Actix-Web service application by making example queries. 5 | //! 6 | //! To verify the example works, start the server in one terminal: 7 | //! ```sh 8 | //! cargo run --example server 9 | //! ``` 10 | //! 11 | //! In another terminal, launch the client: 12 | //! ```sh 13 | //! cargo run --example client 14 | //! ``` 15 | 16 | use base64::{engine::Engine as _, prelude::BASE64_STANDARD}; 17 | use env_logger::Env; 18 | use log::info; 19 | use ppoprf::ppoprf; 20 | use reqwest::blocking::Client as HttpClient; 21 | use serde::{Deserialize, Serialize}; 22 | 23 | /// Fetch the server identification string. 24 | /// 25 | /// Acts as a basic availability ping. 26 | fn fetch_ident(url: &str) -> reqwest::Result<()> { 27 | let res = HttpClient::new().get(url).send()?; 28 | let status = res.status(); 29 | let text = res.text()?; 30 | 31 | info!("{} - {}", status, text); 32 | 33 | Ok(()) 34 | } 35 | 36 | /// Explicit query body. 37 | #[derive(Serialize)] 38 | struct Query { 39 | name: String, 40 | points: Vec, 41 | } 42 | 43 | /// Explicit response body. 44 | #[derive(Deserialize)] 45 | struct Response { 46 | name: String, 47 | results: Vec, 48 | } 49 | 50 | /// Fetch randomness from the server. 51 | /// 52 | /// Acts as a basic round-trip test. 53 | fn fetch_randomness(url: &str) -> reqwest::Result<()> { 54 | // Construct a blinded message hash. 55 | let message = "ppoprf test client"; 56 | let (blinded_message, r) = ppoprf::Client::blind(message.as_bytes()); 57 | // Submit it to the server. 58 | let query = Query { 59 | name: "example client".into(), 60 | points: vec![blinded_message], 61 | }; 62 | let res = HttpClient::new().post(url).json(&query).send()?; 63 | let status = res.status(); 64 | let result = res.json::()?; 65 | let results = result.results; 66 | 67 | // We only submit one hash; confirm the server returned the same. 68 | info!( 69 | "{} {} - {} points returned", 70 | status, 71 | result.name, 72 | results.len() 73 | ); 74 | assert_eq!( 75 | query.points.len(), 76 | results.len(), 77 | "Server returned a different number of points!" 78 | ); 79 | assert_eq!(results.len(), 1, "Expected one point!"); 80 | if let Some(result) = results.first() { 81 | // Unblind the message hash. 82 | let unblinded = ppoprf::Client::unblind(&result.output, &r); 83 | // Next we would finalize the unblinded point to produce 84 | // value needed for the next step of the Star protocol. 85 | // 86 | // This requires knowning the epoch metadata tag the server 87 | // used, which we don't currently coordinate, so skip this step. 88 | //let mut out = vec![0u8; ppoprf::COMPRESSED_POINT_LEN]; 89 | //ppoprf::Client::finalize(message.as_bytes(), &md, &unblinded, &mut out); 90 | let point = BASE64_STANDARD.encode(unblinded.as_bytes()); 91 | let proof = result.proof.is_some(); 92 | let meta = if proof { " proof" } else { "" }; 93 | info!(" {}{}", &point, &meta); 94 | } 95 | 96 | Ok(()) 97 | } 98 | 99 | fn main() { 100 | let url = "http://localhost:8080"; 101 | 102 | env_logger::init_from_env(Env::default().default_filter_or("info")); 103 | 104 | info!("Contacting server at {}", url); 105 | fetch_ident(url).unwrap(); 106 | fetch_randomness(url).unwrap(); 107 | } 108 | -------------------------------------------------------------------------------- /ppoprf/examples/server.rs: -------------------------------------------------------------------------------- 1 | //! Example ppoprf randomness web service. 2 | //! 3 | //! This wraps the ppoprf evaluation function in an Actix-Web 4 | //! service application so it can be accessed over https. 5 | //! 6 | //! To verify the example works, start the server in one terminal: 7 | //! ```sh 8 | //! cargo run --example server 9 | //! ``` 10 | //! 11 | //! In another terminal, verify the GET method returns a service 12 | //! identification: 13 | //! ```sh 14 | //! curl --silent localhost:8080 15 | //! ``` 16 | //! 17 | //! Finally verify the POST method returns an altered point: 18 | //! ```sh 19 | //! curl --silent localhost:8080 \ 20 | //! --header 'Content-Type: application/json' \ 21 | //! --data '{"name":"STAR", "points": [ 22 | //! [ 226, 242, 174, 10, 106, 188, 78, 113, 23 | //! 168, 132, 169, 97, 197, 0, 81, 95, 24 | //! 88, 227, 11, 106, 165, 130, 221, 141, 25 | //! 182, 166, 89, 69, 224, 141, 45, 118 ] 26 | //! ]}' 27 | //! ``` 28 | 29 | use dotenvy::dotenv; 30 | use env_logger::Env; 31 | use log::{info, warn}; 32 | use serde::{Deserialize, Serialize}; 33 | use warp::http::StatusCode; 34 | use warp::Filter; 35 | 36 | use std::collections::VecDeque; 37 | use std::convert::Infallible; 38 | use std::env; 39 | use std::sync::{Arc, RwLock}; 40 | 41 | use ppoprf::ppoprf; 42 | 43 | const DEFAULT_EPOCH_DURATION: u64 = 5; 44 | const DEFAULT_MDS: &str = "116;117;118;119;120"; 45 | const EPOCH_DURATION_ENV_KEY: &str = "EPOCH_DURATION"; 46 | const MDS_ENV_KEY: &str = "METADATA_TAGS"; 47 | 48 | /// Shared randomness server state 49 | struct ServerState { 50 | prf_server: ppoprf::Server, 51 | active_md: u8, 52 | future_mds: VecDeque, 53 | } 54 | /// Wrapped state for access within service tasks 55 | /// We use an RWLock to handle the infrequent puncture events. 56 | /// Only read access is necessary to answer queries. 57 | type State = Arc>; 58 | 59 | /// Decorator to clone state into a warp::Filter. 60 | fn with_state( 61 | state: State, 62 | ) -> impl Filter + Clone { 63 | warp::any().map(move || Arc::clone(&state)) 64 | } 65 | 66 | /// PPOPRF evaluation request from the client 67 | #[derive(Deserialize)] 68 | struct EvalRequest { 69 | name: String, 70 | points: Vec, 71 | } 72 | 73 | /// PPOPRF evaluation result returned by the server 74 | #[derive(Serialize)] 75 | struct EvalResponse { 76 | name: String, 77 | results: Vec, 78 | } 79 | 80 | #[derive(Serialize)] 81 | struct ServerErrorResponse { 82 | error: String, 83 | } 84 | 85 | /// Simple string to identify the server. 86 | fn help() -> &'static str { 87 | concat!( 88 | "STAR protocol randomness server.\n", 89 | "See https://arxiv.org/abs/2109.10074 for more information.\n" 90 | ) 91 | } 92 | 93 | /// Process a PPOPRF evaluation request from the client. 94 | async fn eval( 95 | data: EvalRequest, 96 | state: State, 97 | ) -> Result { 98 | let state = state.read().unwrap(); 99 | 100 | // Pass each point from the client through the ppoprf. 101 | let result: Result, ppoprf::PPRFError> = data 102 | .points 103 | .iter() 104 | .map(|p| state.prf_server.eval(p, state.active_md, false)) 105 | .collect(); 106 | 107 | // Format the results. 108 | match result { 109 | Ok(results) => Ok(warp::reply::with_status( 110 | warp::reply::json(&EvalResponse { 111 | name: data.name, 112 | results, 113 | }), 114 | StatusCode::OK, 115 | )), 116 | Err(error) => Ok(warp::reply::with_status( 117 | warp::reply::json(&ServerErrorResponse { 118 | error: format!("{error}"), 119 | }), 120 | StatusCode::INTERNAL_SERVER_ERROR, 121 | )), 122 | } 123 | } 124 | 125 | #[tokio::main] 126 | async fn main() { 127 | dotenv().ok(); 128 | 129 | let host = "localhost"; 130 | let port = 8080; 131 | 132 | env_logger::init_from_env(Env::default().default_filter_or("info")); 133 | info!("Server configured on {host} port {port}"); 134 | 135 | // Metadata tags marking each randomness epoch. 136 | let mds_str = match env::var(MDS_ENV_KEY) { 137 | Ok(val) => val, 138 | Err(_) => { 139 | info!( 140 | "{} env var not defined, using default: {}", 141 | MDS_ENV_KEY, DEFAULT_MDS 142 | ); 143 | DEFAULT_MDS.to_string() 144 | } 145 | }; 146 | let mds: Vec = mds_str 147 | .split(';') 148 | .map(|y| { 149 | y.parse().expect( 150 | "Could not parse metadata tags. Must contain 8-bit unsigned values!", 151 | ) 152 | }) 153 | .collect(); 154 | 155 | // Time interval between puncturing each successive md. 156 | let epoch = 157 | std::time::Duration::from_secs(match env::var(EPOCH_DURATION_ENV_KEY) { 158 | Ok(val) => val.parse().expect( 159 | "Could not parse epoch duration. It must be a positive number!", 160 | ), 161 | Err(_) => { 162 | info!( 163 | "{} env var not defined, using default: {} seconds", 164 | EPOCH_DURATION_ENV_KEY, DEFAULT_EPOCH_DURATION 165 | ); 166 | DEFAULT_EPOCH_DURATION 167 | } 168 | }); 169 | 170 | // Initialize shared server state. 171 | let state = Arc::new(RwLock::new(ServerState { 172 | prf_server: ppoprf::Server::new(mds.clone()).unwrap(), 173 | active_md: mds[0], 174 | future_mds: VecDeque::from(mds[1..].to_vec()), 175 | })); 176 | info!("PPOPRF initialized with epoch metadata tags {:?}", &mds); 177 | 178 | // Spawn a background task. 179 | let background_state = state.clone(); 180 | tokio::spawn(async move { 181 | info!( 182 | "Background task will rotate epoch every {} seconds", 183 | epoch.as_secs() 184 | ); 185 | // Loop over the list of configured epoch metadata tags. 186 | for &md in &mds { 187 | info!( 188 | "Epoch tag now '{:?}'; next rotation in {} seconds", 189 | md, 190 | epoch.as_secs() 191 | ); 192 | // Wait for the end of an epoch. 193 | tokio::time::sleep(epoch).await; 194 | if let Ok(mut state) = background_state.write() { 195 | info!("Epoch rotation: puncturing '{:?}'", md); 196 | state.prf_server.puncture(md).unwrap(); 197 | let new_md = state.future_mds.pop_front().unwrap(); 198 | state.active_md = new_md; 199 | } 200 | } 201 | warn!("All epoch tags punctured! No further evaluations possible."); 202 | }); 203 | 204 | // Warp web server framework routes. 205 | let info = warp::get().map(help); 206 | let rand = warp::post() 207 | .and(warp::body::content_length_limit(8 * 1024)) 208 | .and(warp::body::json()) 209 | .and(with_state(state)) 210 | .and_then(eval); 211 | let routes = rand.or(info); 212 | 213 | // Run server until exit. 214 | warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; 215 | } 216 | -------------------------------------------------------------------------------- /ppoprf/src/ggm.rs: -------------------------------------------------------------------------------- 1 | //! This module implements the [Goldwasser-Goldreich-Micali 2 | //! PRF](https://crypto.stanford.edu/pbc/notes/crypto/ggm.html), along 3 | //! with extended functionality that allows puncturing inputs from 4 | //! secret keys. 5 | 6 | use std::fmt; 7 | 8 | use super::{PPRFError, PPRF}; 9 | use crate::strobe_rng::StrobeRng; 10 | use bitvec::prelude::*; 11 | use rand::rngs::OsRng; 12 | use rand::Rng; 13 | use serde::{Deserialize, Serialize}; 14 | use strobe_rs::{SecParam, Strobe}; 15 | 16 | use zeroize::{Zeroize, ZeroizeOnDrop}; 17 | 18 | #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] 19 | struct Prefix { 20 | bits: BitVec, 21 | } 22 | 23 | impl Prefix { 24 | fn new(bits: BitVec) -> Self { 25 | Prefix { bits } 26 | } 27 | 28 | fn len(&self) -> usize { 29 | self.bits.len() 30 | } 31 | } 32 | 33 | impl fmt::Debug for Prefix { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | f.debug_struct("Prefix") 36 | .field("bits", &self.bits.as_raw_slice().to_vec()) 37 | .finish() 38 | } 39 | } 40 | 41 | #[derive( 42 | Debug, Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize, PartialEq, Eq, 43 | )] 44 | struct GGMPseudorandomGenerator { 45 | key: [u8; 32], 46 | } 47 | 48 | impl GGMPseudorandomGenerator { 49 | fn setup() -> Self { 50 | let mut t = Strobe::new(b"ggm key gen (ppoprf)", SecParam::B128); 51 | t.key(&sample_secret(), false); 52 | let mut rng: StrobeRng = t.into(); 53 | let mut s_key = [0u8; 32]; 54 | rng.fill(&mut s_key); 55 | GGMPseudorandomGenerator { key: s_key } 56 | } 57 | 58 | fn eval(&self, input: &[u8], output: &mut [u8]) { 59 | let mut t = Strobe::new(b"ggm eval (ppoprf)", SecParam::B128); 60 | t.key(&self.key, false); 61 | t.ad(input, false); 62 | let mut rng: StrobeRng = t.into(); 63 | rng.fill(output); 64 | } 65 | } 66 | 67 | #[derive( 68 | Debug, Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize, Eq, PartialEq, 69 | )] 70 | pub(crate) struct GGMPuncturableKey { 71 | prgs: Vec, 72 | #[zeroize(skip)] 73 | prefixes: Vec<(Prefix, Vec)>, 74 | #[zeroize(skip)] 75 | punctured: Vec, 76 | } 77 | 78 | // TODO: remove copies/clones 79 | impl GGMPuncturableKey { 80 | fn new() -> Self { 81 | let secret = sample_secret(); 82 | // Setup PRGs and initial tree 83 | let prg0 = GGMPseudorandomGenerator::setup(); 84 | let mut out0 = vec![0u8; 32]; 85 | prg0.eval(&secret, &mut out0); 86 | let prg1 = GGMPseudorandomGenerator::setup(); 87 | let mut out1 = vec![0u8; 32]; 88 | prg1.eval(&secret, &mut out1); 89 | GGMPuncturableKey { 90 | prgs: vec![prg0, prg1], 91 | prefixes: vec![ 92 | (Prefix::new(bits![0].to_bitvec()), out0), 93 | (Prefix::new(bits![1].to_bitvec()), out1), 94 | ], 95 | punctured: vec![], 96 | } 97 | } 98 | 99 | fn find_prefix(&self, bv: &BitVec) -> Result<(Prefix, Vec), PPRFError> { 100 | let key_prefixes = self.prefixes.clone(); 101 | for prefix in key_prefixes { 102 | let bits = &prefix.0.bits; 103 | if bv.starts_with(bits) { 104 | return Ok(prefix); 105 | } 106 | } 107 | Err(PPRFError::NoPrefixFound) 108 | } 109 | 110 | fn puncture( 111 | &mut self, 112 | pfx: &Prefix, 113 | to_punc: &Prefix, 114 | new_prefixes: Vec<(Prefix, Vec)>, 115 | ) -> Result<(), PPRFError> { 116 | if self.punctured.iter().any(|p| p.bits == pfx.bits) { 117 | return Err(PPRFError::AlreadyPunctured); 118 | } 119 | if let Some(index) = self.prefixes.iter().position(|p| p.0.bits == pfx.bits) 120 | { 121 | self.prefixes.remove(index); 122 | if !new_prefixes.is_empty() { 123 | self.prefixes.extend(new_prefixes); 124 | } 125 | self.punctured.push(to_punc.clone()); 126 | return Ok(()); 127 | } 128 | Err(PPRFError::NoPrefixFound) 129 | } 130 | } 131 | 132 | #[derive(Clone, Zeroize, ZeroizeOnDrop)] 133 | pub struct GGM { 134 | inp_len: usize, 135 | pub(crate) key: GGMPuncturableKey, 136 | } 137 | 138 | impl GGM { 139 | fn bit_eval(&self, bits: &BitVec, prg_inp: &[u8], output: &mut [u8]) { 140 | let mut eval = prg_inp.to_vec(); 141 | for bit in bits { 142 | let prg: &GGMPseudorandomGenerator = if *bit { 143 | &self.key.prgs[1] 144 | } else { 145 | &self.key.prgs[0] 146 | }; 147 | prg.eval(&eval.clone(), &mut eval); 148 | } 149 | output.copy_from_slice(&eval); 150 | } 151 | 152 | fn partial_eval( 153 | &self, 154 | input_bits: &mut BitVec, 155 | output: &mut [u8], 156 | ) -> Result<(), PPRFError> { 157 | let res = self.key.find_prefix(input_bits); 158 | if let Ok(pfx) = res { 159 | let tail = pfx.1; 160 | let (_, right) = input_bits.split_at(pfx.0.bits.len()); 161 | self.bit_eval(&right.to_bitvec(), &tail, output); 162 | return Ok(()); 163 | } 164 | Err(PPRFError::NoPrefixFound) 165 | } 166 | } 167 | 168 | impl PPRF for GGM { 169 | fn setup() -> Self { 170 | GGM { 171 | inp_len: 1, 172 | key: GGMPuncturableKey::new(), 173 | } 174 | } 175 | 176 | fn eval(&self, input: &[u8], output: &mut [u8]) -> Result<(), PPRFError> { 177 | if input.len() != self.inp_len { 178 | return Err(PPRFError::BadInputLength { 179 | actual: input.len(), 180 | expected: self.inp_len, 181 | }); 182 | } 183 | let mut input_bits = 184 | bvcast_u8_to_usize(&BitVec::<_, Lsb0>::from_slice(input)); 185 | self.partial_eval(&mut input_bits, output) 186 | } 187 | 188 | // Disable clippy lint false positive on `iter_bv` with Rust 1.70.0. 189 | #[allow(clippy::redundant_clone)] 190 | fn puncture(&mut self, input: &[u8]) -> Result<(), PPRFError> { 191 | if input.len() != self.inp_len { 192 | return Err(PPRFError::BadInputLength { 193 | actual: input.len(), 194 | expected: self.inp_len, 195 | }); 196 | } 197 | let bv = bvcast_u8_to_usize(&BitVec::<_, Lsb0>::from_slice(input)); 198 | let pfx = self.key.find_prefix(&bv)?; 199 | let pfx_len = pfx.0.len(); 200 | 201 | // If the prefix is smaller than the current input, then we 202 | // need to recompute some parts of the tree. Otherwise we 203 | // just remove the prefix entirely. 204 | let mut new_pfxs: Vec<(Prefix, Vec)> = Vec::new(); 205 | if pfx_len != bv.len() { 206 | let mut iter_bv = bv.clone(); 207 | for i in (0..bv.len()).rev() { 208 | if let Some((last, rest)) = iter_bv.clone().split_last() { 209 | let mut cbv = iter_bv.clone(); 210 | cbv.set(i, !*last); 211 | let mut out = vec![0u8; 32]; 212 | let (_, split) = cbv.split_at(pfx_len); 213 | self.bit_eval(&split.to_bitvec(), &pfx.1, &mut out); 214 | new_pfxs.push((Prefix::new(cbv), out)); 215 | if rest.len() == pfx_len { 216 | // we don't want to recompute any further 217 | break; 218 | } 219 | iter_bv = rest.to_bitvec(); 220 | } else { 221 | return Err(PPRFError::UnexpectedEndOfBv); 222 | } 223 | } 224 | } 225 | 226 | self.key.puncture(&pfx.0, &Prefix::new(bv), new_pfxs) 227 | } 228 | } 229 | 230 | fn sample_secret() -> Vec { 231 | let mut out = vec![0u8; 32]; 232 | OsRng.fill(out.as_mut_slice()); 233 | out 234 | } 235 | 236 | fn bvcast_u8_to_usize( 237 | bv_u8: &BitVec, 238 | ) -> BitVec { 239 | let mut bv_us = BitVec::with_capacity(bv_u8.len()); 240 | for i in 0..bv_u8.len() { 241 | bv_us.push(bv_u8[i]); 242 | } 243 | bv_us 244 | } 245 | 246 | #[cfg(test)] 247 | mod tests { 248 | use super::*; 249 | 250 | #[test] 251 | fn eval() -> Result<(), PPRFError> { 252 | let ggm = GGM::setup(); 253 | let x0 = [8u8]; 254 | let x1 = [7u8]; 255 | let mut out = [0u8; 32]; 256 | ggm.eval(&x0, &mut out)?; 257 | ggm.eval(&x1, &mut out)?; 258 | Ok(()) 259 | } 260 | 261 | #[test] 262 | fn puncture_fail_eval() -> Result<(), PPRFError> { 263 | let mut ggm = GGM::setup(); 264 | let x0 = [8u8]; 265 | let mut out = [0u8; 32]; 266 | ggm.eval(&x0, &mut out)?; 267 | ggm.puncture(&x0)?; 268 | // next step should error out 269 | assert!(matches!( 270 | ggm.eval(&x0, &mut out), 271 | Err(PPRFError::NoPrefixFound) 272 | )); 273 | Ok(()) 274 | } 275 | 276 | #[test] 277 | fn mult_puncture_fail_eval() -> Result<(), PPRFError> { 278 | let mut ggm = GGM::setup(); 279 | let x0 = [0u8]; 280 | let x1 = [1u8]; 281 | ggm.puncture(&x0)?; 282 | ggm.puncture(&x1)?; 283 | // next step should error out 284 | assert!(matches!( 285 | ggm.eval(&x0, &mut [0u8; 32]), 286 | Err(PPRFError::NoPrefixFound) 287 | )); 288 | Ok(()) 289 | } 290 | 291 | #[test] 292 | fn puncture_eval_consistent() -> Result<(), PPRFError> { 293 | let mut ggm = GGM::setup(); 294 | let inputs = [[2u8], [4u8], [8u8], [16u8], [32u8], [64u8], [128u8]]; 295 | let x0 = [0u8]; 296 | let mut outputs_b4 = vec![vec![0u8; 1]; inputs.len()]; 297 | let mut outputs_after = vec![vec![0u8; 1]; inputs.len()]; 298 | for (i, x) in inputs.iter().enumerate() { 299 | let mut out = vec![0u8; 32]; 300 | ggm.eval(x, &mut out)?; 301 | outputs_b4[i] = out; 302 | } 303 | ggm.puncture(&x0)?; 304 | for (i, x) in inputs.iter().enumerate() { 305 | let mut out = vec![0u8; 32]; 306 | ggm.eval(x, &mut out)?; 307 | outputs_after[i] = out; 308 | } 309 | for (i, o) in outputs_b4.iter().enumerate() { 310 | assert_eq!(o, &outputs_after[i]); 311 | } 312 | Ok(()) 313 | } 314 | 315 | #[test] 316 | fn multiple_puncture() -> Result<(), PPRFError> { 317 | let mut ggm = GGM::setup(); 318 | let inputs = [[2u8], [4u8], [8u8], [16u8], [32u8], [64u8], [128u8]]; 319 | let mut outputs_b4 = vec![vec![0u8; 1]; inputs.len()]; 320 | let mut outputs_after = vec![vec![0u8; 1]; inputs.len()]; 321 | for (i, x) in inputs.iter().enumerate() { 322 | let mut out = vec![0u8; 32]; 323 | ggm.eval(x, &mut out)?; 324 | outputs_b4[i] = out; 325 | } 326 | let x0 = [0u8]; 327 | let x1 = [1u8]; 328 | ggm.puncture(&x0)?; 329 | for (i, x) in inputs.iter().enumerate() { 330 | let mut out = vec![0u8; 32]; 331 | ggm.eval(x, &mut out)?; 332 | outputs_after[i] = out; 333 | } 334 | for (i, o) in outputs_b4.iter().enumerate() { 335 | assert_eq!(o, &outputs_after[i]); 336 | } 337 | ggm.puncture(&x1)?; 338 | for (i, x) in inputs.iter().enumerate() { 339 | let mut out = vec![0u8; 32]; 340 | ggm.eval(x, &mut out)?; 341 | outputs_after[i] = out; 342 | } 343 | for (i, o) in outputs_b4.iter().enumerate() { 344 | assert_eq!(o, &outputs_after[i]); 345 | } 346 | Ok(()) 347 | } 348 | 349 | #[test] 350 | fn puncture_all() -> Result<(), PPRFError> { 351 | let mut inputs = Vec::new(); 352 | for i in 0..255 { 353 | inputs.push(vec![i as u8]); 354 | } 355 | let mut ggm = GGM::setup(); 356 | for x in &inputs { 357 | ggm.puncture(x)?; 358 | } 359 | Ok(()) 360 | } 361 | 362 | #[test] 363 | fn casting() { 364 | let bv_0 = bits![0].to_bitvec(); 365 | let bv_1 = bvcast_u8_to_usize(&BitVec::<_, Lsb0>::from_slice(&[4])); 366 | assert_eq!(bv_0.len(), 1); 367 | assert_eq!(bv_1.len(), 8); 368 | assert!(bv_1.starts_with(&bv_0)); 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /ppoprf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the combined functionality for producing a 2 | //! puncturable partially oblivious pseudorandom function (PPOPRF) 3 | //! protocol. The protocol combines the PPOPRF of [Tyagi et 4 | //! al.](https://eprint.iacr.org/2021/864.pdf) with the classic GGM 5 | //! puncturable PRF. 6 | //! 7 | //! The result is a POPRF that can provide forward-security guarantees 8 | //! related to the pseudorandomness of client-side outputs, by allowing 9 | //! the puncturing of metadata tags from the server secret key. Such 10 | //! guarantees hold when clients reveal POPRF outputs for a metadata tag 11 | //! `t`, after `t` has been punctured from the secret key. This 12 | //! functionality is used to provide forward-secure randomness to 13 | //! clients in the STAR protocol. 14 | 15 | pub mod ggm; 16 | pub mod ppoprf; 17 | 18 | mod strobe_rng; 19 | 20 | use derive_more::{Display, Error}; 21 | 22 | #[derive(Debug, Error, Display)] 23 | pub enum PPRFError { 24 | #[display(fmt = "Specified tag ({md}) is not a valid metadata tag")] 25 | BadTag { md: u8 }, 26 | #[display(fmt = "No prefix found")] 27 | NoPrefixFound, 28 | #[display(fmt = "Tag already punctured")] 29 | AlreadyPunctured, 30 | #[display( 31 | fmt = "Input length ({actual}) does not match input param ({expected})" 32 | )] 33 | BadInputLength { actual: usize, expected: usize }, 34 | #[display(fmt = "Unexpected end of bv")] 35 | UnexpectedEndOfBv, 36 | #[display(fmt = "Bincode serialization error: {_0}")] 37 | Bincode(bincode::Error), 38 | #[display(fmt = "Serialized data exceeds size limit")] 39 | SerializedDataTooBig, 40 | #[display(fmt = "Bad compressed ristretto point encoding")] 41 | BadPointEncoding, 42 | } 43 | 44 | pub trait PPRF { 45 | fn setup() -> Self; 46 | fn eval(&self, input: &[u8], output: &mut [u8]) -> Result<(), PPRFError>; 47 | fn puncture(&mut self, input: &[u8]) -> Result<(), PPRFError>; 48 | } 49 | -------------------------------------------------------------------------------- /ppoprf/src/strobe_rng.rs: -------------------------------------------------------------------------------- 1 | /// RngCore impl for the Strobe hash 2 | /// 3 | /// FIXME: This should be submitted upstream as a feature-gated 4 | /// extension. Failing that, publish as a separate helper crate 5 | /// to avoid this code duplication. 6 | use rand_core::{CryptoRng, RngCore}; 7 | use strobe_rs::Strobe; 8 | 9 | /// StrobeRng implements the RngCore trait by using STROBE as an entropy pool 10 | pub struct StrobeRng { 11 | strobe: Strobe, 12 | } 13 | 14 | impl From for StrobeRng { 15 | fn from(strobe: Strobe) -> Self { 16 | StrobeRng { strobe } 17 | } 18 | } 19 | 20 | impl RngCore for StrobeRng { 21 | fn next_u32(&mut self) -> u32 { 22 | rand_core::impls::next_u32_via_fill(self) 23 | } 24 | 25 | fn next_u64(&mut self) -> u64 { 26 | rand_core::impls::next_u64_via_fill(self) 27 | } 28 | 29 | fn fill_bytes(&mut self, dest: &mut [u8]) { 30 | let dest_len = (dest.len() as u32).to_le_bytes(); 31 | self.strobe.meta_ad(&dest_len, false); 32 | self.strobe.prf(dest, false); 33 | } 34 | 35 | fn try_fill_bytes( 36 | &mut self, 37 | dest: &mut [u8], 38 | ) -> Result<(), rand_core::Error> { 39 | self.fill_bytes(dest); 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl CryptoRng for StrobeRng {} 45 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "local>brave/renovate-config" 4 | ], 5 | "packageRules": [ 6 | { 7 | "description": "Stay compatible with older deps for brave-core", 8 | "matchDepTypes": ["dependencies"], 9 | "matchUpdateTypes": ["minor", "patch"], 10 | "enabled": false 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | -------------------------------------------------------------------------------- /sharks/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .vscode 6 | -------------------------------------------------------------------------------- /sharks/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.6.1] - 2023-08-17 8 | 9 | - Fix no_std build 10 | - Fix preallocation calculation 11 | - Test and benchmark code cleanup 12 | - Fix false positive issue with the `recover` fuzzing harness 13 | - Minor documentation, code, and ci improvements 14 | 15 | ## [0.6.0] - 2023-07-18 16 | 17 | - Fork adapted to the needs of the STAR protocol 18 | - Use a smaller (129-bit) field for better performance 19 | - Choose Sophie Germain prime (2^128 + 12451) 20 | - Various code cleanup and formatting 21 | - Update dependencies 22 | 23 | ## [0.5.0] - 2021-03-14 24 | ### Added 25 | - Zeroize memory on drop for generated secret shares 26 | 27 | ## [0.4.3] - 2021-02-04 28 | ### Changed 29 | - Upgraded project dependencies 30 | 31 | ## [0.4.2] - 2020-08-03 32 | ### Fixed 33 | - Small fix in docs 34 | 35 | ## [0.4.1] - 2020-04-23 36 | ### Added 37 | - Fuzz tests 38 | 39 | ### Fixed 40 | - Unexpected panic when trying to recover secret from different length shares 41 | - Unexpected panic when trying to convert less than 2 bytes to `Share` 42 | 43 | ## [0.4.0] - 2020-04-02 44 | ### Added 45 | - It is now possible to compile without `std` with `--no-default-features` 46 | 47 | ## [0.3.3] - 2020-03-23 48 | ### Changed 49 | - Fix codecov badge 50 | 51 | ## [0.3.2] - 2020-03-09 52 | ### Changed 53 | - Share structs now derives the `Clone` trait 54 | 55 | ## [0.3.1] - 2020-01-23 56 | ### Changed 57 | - Sharks recover method now accepts any iterable collection 58 | 59 | ## [0.3.0] - 2020-01-22 60 | ### Added 61 | - Share struct which allows to convert from/to byte vectors 62 | 63 | ### Changed 64 | - Methods use the new Share struct, instead of (GF245, Vec) tuples 65 | 66 | ## [0.2.0] - 2020-01-21 67 | ### Added 68 | - Computations performed over GF256 (much faster) 69 | - Secret can now be arbitrarily long 70 | 71 | ### Changed 72 | - Some method names and docs 73 | - Maximum number of shares enforced by Rust static types instead of conditional branching 74 | 75 | ### Removed 76 | - Modular arithmetic around Mersenne primes 77 | 78 | ## [0.1.1] - 2020-01-13 79 | ### Fixed 80 | - Typo in cargo description 81 | 82 | ### Removed 83 | - Maintenance badges in cargo file 84 | 85 | ## [0.1.0] - 2020-01-13 86 | ### Added 87 | - Initial version 88 | -------------------------------------------------------------------------------- /sharks/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyrights in the Sharks project are retained by their contributors. No 2 | copyright assignment is required to contribute to the Sharks project. 3 | 4 | For full authorship information, see the version control history. 5 | 6 | Except as otherwise noted (below and/or in individual files), Sharks is 7 | licensed under the Apache License, Version 2.0 or 8 | or the MIT license 9 | or , at your option. 10 | 11 | The Sharks project includes code from the Rust project 12 | published under these same licenses. 13 | -------------------------------------------------------------------------------- /sharks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "star-sharks" 3 | version = "0.6.1" 4 | authors = ["Aitor Ruano ", "Alex Davidson ", "Ralph Giles "] 5 | description = "Shamir's Secret Sharing library for the STAR protocol" 6 | repository = "https://github.com/brave/sta-rs" 7 | readme = "README.md" 8 | keywords = ["shamir", "secret", "sharing", "share", "crypto"] 9 | categories = ["algorithms", "cryptography", "mathematics"] 10 | license = "MIT/Apache-2.0" 11 | edition = "2018" 12 | 13 | [features] 14 | default = ["std", "zeroize_memory"] 15 | std = ["rand/std", "rand/std_rng"] 16 | fuzzing = ["std", "arbitrary"] 17 | zeroize_memory = ["zeroize"] 18 | 19 | [dependencies] 20 | rand = { version = "0.8.5", default-features = false } 21 | arbitrary = { version = "1.3.0", features = ["derive"], optional = true } 22 | zeroize = { version = "1.5.5", features = ["zeroize_derive"], optional = true } 23 | ff = { version = "0.13", features = ["derive"] } 24 | bitvec = { version = "1.0.1", default-features = false } 25 | byteorder = { version = "1", default-features = false } 26 | 27 | [dev-dependencies] 28 | criterion = "0.5" 29 | rand_chacha = "0.3" 30 | 31 | [[bench]] 32 | name = "benchmarks" 33 | harness = false 34 | -------------------------------------------------------------------------------- /sharks/LICENSE-APACHE: -------------------------------------------------------------------------------- 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 2020 Aitor Ruano Miralles 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 | -------------------------------------------------------------------------------- /sharks/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aitor Ruano Miralles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sharks/README.md: -------------------------------------------------------------------------------- 1 | # Sharks 2 | 3 | [![Build](https://github.com/brave/sta-rs/workflows/Tests/badge.svg?branch=main)](https://github.com/brave/sta-rs/actions) 4 | [![Crates](https://img.shields.io/crates/v/star-sharks.svg)](https://crates.io/crates/star-sharks) 5 | [![Docs](https://docs.rs/star-sharks/badge.svg)](https://docs.rs/star-sharks) 6 | 7 | Fast, small and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library in Rust. 8 | This is a fork of the original [sharks crate](https://crates.io/crates/sharks) 9 | used with the STAR protocol. 10 | 11 | Documentation: 12 | - [API reference (docs.rs)](https://docs.rs/star-sharks) 13 | 14 | ## Usage 15 | 16 | Add this to your `Cargo.toml`: 17 | 18 | ```toml 19 | [dependencies] 20 | star-sharks = "0.6" 21 | ``` 22 | 23 | If your environment doesn't support `std`: 24 | 25 | ```toml 26 | [dependencies] 27 | star-sharks = { version = "0.6", default-features = false } 28 | ``` 29 | 30 | To get started using Sharks, see the [reference docs](https://docs.rs/star-sharks) 31 | 32 | ## Features 33 | 34 | ### Developer friendly 35 | The API is simple and to the point, with minimal configuration. 36 | 37 | ### Fast and small 38 | The code is as idiomatic and clean as possible, with minimum external dependencies. 39 | 40 | ### Secure by design 41 | The implementation forbids the user to choose parameters that would result in an insecure application, 42 | like generating more shares than what's allowed by the finite field length. 43 | 44 | This implementation uses a [Sophie Germain prime](https://en.wikipedia.org/wiki/Safe_and_Sophie_Germain_primes) (2^128 + 12451). 45 | 46 | ## Testing 47 | 48 | This crate contains both unit and benchmark tests (as well as the examples included in the docs). 49 | You can run them with `cargo test` and `cargo bench`. 50 | 51 | # Contributing 52 | 53 | If you find a bug or would like a new feature, [open a new issue](https://github.com/brave/sta-rs/issues/new). Please see the [security page](https://github.com/brave/sta-rs/blob/main/SECURITY.md) for information on reporting vulnerabilities. 54 | 55 | # License 56 | 57 | Sharks is distributed under the terms of both the MIT license and the 58 | Apache License (Version 2.0). 59 | 60 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and 61 | [COPYRIGHT](COPYRIGHT) for details. 62 | -------------------------------------------------------------------------------- /sharks/benches/benchmarks.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use std::convert::TryFrom; 3 | 4 | use star_sharks::Share; 5 | 6 | #[cfg(feature = "std")] 7 | fn dealer(c: &mut Criterion) { 8 | let sharks = star_sharks::Sharks(255); 9 | let mut dealer = sharks.dealer(&[1]).unwrap(); 10 | 11 | c.bench_function("obtain_shares_dealer", |b| { 12 | b.iter(|| sharks.dealer(black_box(&[1])).unwrap()) 13 | }); 14 | c.bench_function("step_shares_dealer", |b| b.iter(|| dealer.next())); 15 | } 16 | 17 | #[cfg(feature = "std")] 18 | fn recover(c: &mut Criterion) { 19 | let sharks = star_sharks::Sharks(255); 20 | let dealer = sharks.dealer(&[1]).unwrap(); 21 | let shares: Vec = dealer.take(255).collect(); 22 | 23 | c.bench_function("recover_secret", |b| { 24 | b.iter(|| sharks.recover(black_box(shares.as_slice()))) 25 | }); 26 | } 27 | 28 | fn share(c: &mut Criterion) { 29 | let bytes_vec = test_bytes(); 30 | let bytes = bytes_vec.as_slice(); 31 | let share = Share::try_from(bytes).unwrap(); 32 | 33 | c.bench_function("share_from_bytes", |b| { 34 | b.iter(|| Share::try_from(black_box(bytes))) 35 | }); 36 | 37 | c.bench_function("share_to_bytes", |b| { 38 | b.iter(|| Vec::from(black_box(&share))) 39 | }); 40 | } 41 | 42 | fn test_bytes() -> Vec { 43 | let suffix = vec![0u8; star_sharks::FIELD_ELEMENT_LEN - 1]; 44 | let mut bytes = vec![1u8; 1]; 45 | bytes.extend(suffix.clone()); // x coord 46 | bytes.extend(vec![2u8; 1]); 47 | bytes.extend(suffix.clone()); // y coord #1 48 | bytes.extend(vec![3u8; 1]); 49 | bytes.extend(suffix); // y coord #2 50 | bytes 51 | } 52 | 53 | #[cfg(feature = "std")] 54 | criterion_group!(benches, dealer, recover, share); 55 | 56 | #[cfg(not(feature = "std"))] 57 | criterion_group!(benches, share); 58 | 59 | criterion_main!(benches); 60 | -------------------------------------------------------------------------------- /sharks/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "90...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | comment: 18 | layout: "reach,diff,flags,tree" 19 | behavior: default 20 | require_changes: no 21 | -------------------------------------------------------------------------------- /sharks/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /sharks/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "sharks-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.4.7" 14 | arbitrary = { version = "1.3.0", features = ["derive"] } 15 | 16 | [dependencies.star-sharks] 17 | path = ".." 18 | features = ["fuzzing"] 19 | 20 | # Prevent this from interfering with workspaces 21 | [workspace] 22 | members = ["."] 23 | 24 | [[bin]] 25 | name = "deserialize_share" 26 | path = "fuzz_targets/deserialize_share.rs" 27 | 28 | [[bin]] 29 | name = "serialize_share" 30 | path = "fuzz_targets/serialize_share.rs" 31 | 32 | [[bin]] 33 | name = "generate_shares" 34 | path = "fuzz_targets/generate_shares.rs" 35 | 36 | [[bin]] 37 | name = "recover" 38 | path = "fuzz_targets/recover.rs" 39 | -------------------------------------------------------------------------------- /sharks/fuzz/fuzz_targets/deserialize_share.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use core::convert::TryFrom; 3 | use libfuzzer_sys::fuzz_target; 4 | use star_sharks::Share; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _share = Share::try_from(data); 8 | }); 9 | -------------------------------------------------------------------------------- /sharks/fuzz/fuzz_targets/generate_shares.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use arbitrary::Arbitrary; 5 | use star_sharks::{Share, Sharks}; 6 | 7 | #[derive(Debug, Arbitrary)] 8 | // Limit threshold parameters to 16 bits so we don't immediately oom. 9 | struct Parameters { 10 | pub threshold: u16, 11 | pub secret: Vec, 12 | pub n_shares: u16, 13 | } 14 | 15 | fuzz_target!(|params: Parameters| { 16 | let sharks = Sharks(params.threshold.into()); 17 | if let Ok(dealer) = sharks.dealer(¶ms.secret) { 18 | let _shares: Vec = dealer.take(params.n_shares.into()).collect(); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /sharks/fuzz/fuzz_targets/recover.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use arbitrary::Arbitrary; 5 | use star_sharks::{Share, Sharks}; 6 | 7 | #[derive(Debug, Arbitrary)] 8 | // Limit threshhold to 16 bits so we don't exhaust memory. 9 | struct Parameters { 10 | pub threshold: u16, 11 | pub shares: Vec, 12 | } 13 | 14 | fuzz_target!(|params: Parameters| { 15 | let sharks = Sharks(params.threshold.into()); 16 | let _secret = sharks.recover(¶ms.shares); 17 | }); 18 | -------------------------------------------------------------------------------- /sharks/fuzz/fuzz_targets/serialize_share.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use star_sharks::Share; 5 | 6 | fuzz_target!(|share: Share| { 7 | let _data: Vec = (&share).into(); 8 | }); 9 | -------------------------------------------------------------------------------- /sharks/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Fast, small and secure [Shamir's Secret 2 | //! Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) 3 | //! library crate for large finite fields 4 | #![cfg_attr(not(feature = "std"), no_std)] 5 | 6 | extern crate alloc; 7 | 8 | // implement operations using a larger finite field 9 | extern crate ff; 10 | mod share_ff; 11 | 12 | use alloc::collections::BTreeSet; 13 | use alloc::vec::Vec; 14 | use core::convert::TryInto; 15 | 16 | use crate::ff::PrimeField; 17 | pub use share_ff::Evaluator; 18 | pub use share_ff::Share; 19 | pub use share_ff::{get_evaluator, interpolate, random_polynomial}; 20 | pub use share_ff::{Fp, FpRepr, FIELD_ELEMENT_LEN}; 21 | 22 | /// Tuple struct which implements methods to generate shares 23 | /// and recover secrets over a finite field. 24 | /// Its only parameter is the minimum shares threshold. 25 | pub struct Sharks(pub u32); 26 | 27 | impl Sharks { 28 | /// This method is useful when `std` is not available. For typical usage 29 | /// see the `dealer` method. 30 | /// 31 | /// Given a `secret` byte slice, returns an `Iterator` along new shares. 32 | /// A random number generator has to be provided. 33 | /// 34 | /// Example: 35 | /// ``` 36 | /// # use star_sharks::{ Sharks, Share }; 37 | /// # use rand_chacha::rand_core::SeedableRng; 38 | /// # let sharks = Sharks(3); 39 | /// // Obtain an iterator over the shares for secret [1, 2] 40 | /// let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); 41 | /// let dealer = sharks.dealer_rng(&[1, 2], &mut rng).unwrap(); 42 | /// // Get 3 shares 43 | /// let shares: Vec = dealer.take(3).collect(); 44 | pub fn dealer_rng( 45 | &self, 46 | secret: &[u8], 47 | rng: &mut R, 48 | ) -> Result { 49 | let mut polys = Vec::with_capacity(secret.len()); 50 | 51 | let secret_fp_len = secret.len() / FIELD_ELEMENT_LEN; 52 | for i in 0..secret_fp_len { 53 | let element = Fp::from_repr(FpRepr( 54 | secret[i * FIELD_ELEMENT_LEN..(i + 1) * FIELD_ELEMENT_LEN] 55 | .try_into() 56 | .expect("bad chunk"), 57 | )); 58 | if element.is_none().into() { 59 | return Err("Failed to create field element from secret"); 60 | } 61 | let element = element.unwrap(); 62 | polys.push(random_polynomial(element, self.0, rng)); 63 | } 64 | 65 | Ok(get_evaluator(polys)) 66 | } 67 | 68 | /// Given a `secret` byte slice, returns an `Iterator` along new shares. 69 | /// 70 | /// Example: 71 | /// ``` 72 | /// # use star_sharks::{ Sharks, Share }; 73 | /// # let sharks = Sharks(3); 74 | /// // Obtain an iterator over the shares for secret [1, 2] 75 | /// let dealer = sharks.dealer(&[1, 2]).unwrap(); 76 | /// // Get 3 shares 77 | /// let shares: Vec = dealer.take(3).collect(); 78 | #[cfg(feature = "std")] 79 | pub fn dealer(&self, secret: &[u8]) -> Result { 80 | let mut rng = rand::thread_rng(); 81 | self.dealer_rng(secret, &mut rng) 82 | } 83 | 84 | /// Given an iterable collection of shares, recovers the original secret. 85 | /// If the number of distinct shares is less than the minimum threshold an `Err` is returned, 86 | /// otherwise an `Ok` containing the secret. 87 | /// 88 | /// Example: 89 | /// ``` 90 | /// # use star_sharks::{ Sharks, Share }; 91 | /// # use rand_chacha::rand_core::SeedableRng; 92 | /// # let sharks = Sharks(3); 93 | /// # let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); 94 | /// # let dealer = sharks.dealer_rng(&[1], &mut rng).unwrap(); 95 | /// # let mut shares: Vec = dealer.take(3).collect(); 96 | /// // Recover original secret from shares 97 | /// let secret = sharks.recover(&shares); 98 | /// // Secret correctly recovered 99 | /// assert!(secret.is_ok()); 100 | /// // Remove shares for demonstration purposes 101 | /// shares.clear(); 102 | /// let secret = sharks.recover(&shares); 103 | /// // Not enough shares to recover secret 104 | /// assert!(secret.is_err()); 105 | pub fn recover<'a, T>(&self, shares: T) -> Result, &str> 106 | where 107 | T: IntoIterator, 108 | T::IntoIter: Iterator, 109 | { 110 | let mut share_length: Option = None; 111 | let mut keys: BTreeSet> = BTreeSet::new(); 112 | let mut values: Vec = Vec::new(); 113 | 114 | for share in shares.into_iter() { 115 | if share_length.is_none() { 116 | share_length = Some(share.y.len()); 117 | } 118 | 119 | if Some(share.y.len()) != share_length { 120 | return Err("All shares must have the same length"); 121 | } else if keys.insert(share.x.to_repr().as_ref().to_vec()) { 122 | values.push(share.clone()); 123 | } 124 | } 125 | 126 | if keys.is_empty() || (keys.len() < self.0 as usize) { 127 | Err("Not enough shares to recover original secret") 128 | } else { 129 | // We only need the threshold number of shares to recover 130 | interpolate(&values[0..self.0 as usize]) 131 | } 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::{Fp, Share, Sharks}; 138 | use crate::ff::{Field, PrimeField}; 139 | use crate::FIELD_ELEMENT_LEN; 140 | use alloc::{vec, vec::Vec}; 141 | use core::convert::TryFrom; 142 | 143 | impl Sharks { 144 | #[cfg(not(feature = "std"))] 145 | fn make_shares( 146 | &self, 147 | secret: &[u8], 148 | ) -> Result, &str> { 149 | use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; 150 | // CAUTION: Fixed seed for no-std testing. Don't copy this code! 151 | let mut rng = ChaCha8Rng::from_seed([0x90; 32]); 152 | self.dealer_rng(secret, &mut rng) 153 | } 154 | 155 | #[cfg(feature = "std")] 156 | fn make_shares( 157 | &self, 158 | secret: &[u8], 159 | ) -> Result, &str> { 160 | self.dealer(secret) 161 | } 162 | } 163 | 164 | fn fp_one() -> Fp { 165 | Fp::ONE 166 | } 167 | 168 | fn fp_two() -> Fp { 169 | fp_one().double() 170 | } 171 | 172 | fn fp_one_repr() -> Vec { 173 | fp_one().to_repr().as_ref().to_vec() 174 | } 175 | 176 | fn fp_two_repr() -> Vec { 177 | (fp_one().double()).to_repr().as_ref().to_vec() 178 | } 179 | 180 | fn fp_three_repr() -> Vec { 181 | (fp_two() + fp_one()).to_repr().as_ref().to_vec() 182 | } 183 | 184 | fn fp_four_repr() -> Vec { 185 | (fp_two() + fp_two()).to_repr().as_ref().to_vec() 186 | } 187 | 188 | #[test] 189 | fn insufficient_shares() { 190 | let sharks = Sharks(500); 191 | let shares: Vec = sharks 192 | .make_shares(&fp_one_repr()) 193 | .unwrap() 194 | .take(499) 195 | .collect(); 196 | let secret = sharks.recover(&shares); 197 | assert!(secret.is_err()); 198 | } 199 | 200 | #[test] 201 | fn duplicate_shares() { 202 | let sharks = Sharks(500); 203 | let mut shares: Vec = sharks 204 | .make_shares(&fp_one_repr()) 205 | .unwrap() 206 | .take(500) 207 | .collect(); 208 | shares[1] = Share { 209 | x: shares[0].x, 210 | y: shares[0].y.clone(), 211 | }; 212 | let secret = sharks.recover(&shares); 213 | assert!(secret.is_err()); 214 | } 215 | 216 | #[test] 217 | fn integration() { 218 | let sharks = Sharks(500); 219 | let mut input = Vec::new(); 220 | input.extend(fp_one_repr()); 221 | input.extend(fp_two_repr()); 222 | input.extend(fp_three_repr()); 223 | input.extend(fp_four_repr()); 224 | let shares: Vec = 225 | sharks.make_shares(&input).unwrap().take(500).collect(); 226 | let secret = sharks.recover(&shares).unwrap(); 227 | assert_eq!(secret, test_bytes()); 228 | } 229 | 230 | #[test] 231 | #[cfg(feature = "std")] 232 | fn integration_random() { 233 | let sharks = Sharks(40); 234 | let mut rng = rand::thread_rng(); 235 | let mut input = Vec::new(); 236 | input.extend(fp_one_repr()); 237 | input.extend(fp_two_repr()); 238 | input.extend(fp_three_repr()); 239 | input.extend(fp_four_repr()); 240 | let evaluator = sharks.dealer(&input).unwrap(); 241 | let shares: Vec = 242 | core::iter::repeat_with(|| evaluator.gen(&mut rng)) 243 | .take(55) 244 | .collect(); 245 | let secret = sharks.recover(&shares).unwrap(); 246 | assert_eq!(secret, test_bytes()); 247 | } 248 | 249 | fn test_bytes() -> Vec { 250 | let suffix = vec![0u8; FIELD_ELEMENT_LEN - 1]; 251 | let mut bytes = vec![1u8; 1]; 252 | bytes.extend(suffix.clone()); // x coord 253 | bytes.extend(vec![2u8; 1]); 254 | bytes.extend(suffix.clone()); // y coord #1 255 | bytes.extend(vec![3u8; 1]); 256 | bytes.extend(suffix.clone()); // y coord #2 257 | bytes.extend(vec![4u8; 1]); 258 | bytes.extend(suffix); // y coord #3 259 | bytes 260 | } 261 | 262 | #[test] 263 | fn zero_threshold() { 264 | let sharks = Sharks(0); 265 | let testcase = Share::try_from(test_bytes().as_slice()).unwrap(); 266 | let secret = sharks.recover(&vec![testcase]); 267 | assert!(secret.is_err()); 268 | } 269 | 270 | #[test] 271 | #[cfg(feature = "std")] 272 | fn dealer_short_secret() { 273 | let sharks = Sharks(2); 274 | 275 | // one less byte than needed 276 | let secret = [0u8; FIELD_ELEMENT_LEN - 1]; 277 | let _dealer = sharks.dealer(&secret); 278 | 279 | // one more for a short second element 280 | let secret = [1u8; FIELD_ELEMENT_LEN + 1]; 281 | let _dealer = sharks.dealer(&secret); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /sharks/src/share_ff.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::*; 2 | use core::convert::TryFrom; 3 | use core::convert::TryInto; 4 | 5 | #[cfg(feature = "fuzzing")] 6 | use arbitrary::{Arbitrary, Unstructured}; 7 | 8 | #[cfg(feature = "zeroize_memory")] 9 | use zeroize::Zeroize; 10 | 11 | use crate::ff::*; 12 | 13 | pub const FIELD_ELEMENT_LEN: usize = 24; 14 | 15 | #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] 16 | #[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] 17 | #[derive(PrimeField)] 18 | // 2^128 + 12451 (https://eprint.iacr.org/2011/326) 19 | #[PrimeFieldModulus = "340282366920938463463374607431768223907"] 20 | #[PrimeFieldGenerator = "3"] 21 | #[PrimeFieldReprEndianness = "little"] 22 | pub struct Fp([u64; 3]); 23 | 24 | impl From for Vec { 25 | fn from(s: Fp) -> Vec { 26 | s.to_repr().as_ref().to_vec() 27 | } 28 | } 29 | 30 | impl From for Vec { 31 | fn from(s: Fp) -> Vec { 32 | s.0.to_vec() 33 | } 34 | } 35 | 36 | // Finds the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach). 37 | // The expected `shares` argument format is the same as the output by the `get_evaluator´ function. 38 | // Where each (key, value) pair corresponds to one share, where the key is the `x` and the value is a vector of `y`, 39 | // where each element corresponds to one of the secret's byte chunks. 40 | pub fn interpolate(shares: &[Share]) -> Result, &'static str> { 41 | if shares.is_empty() { 42 | return Err("Need at least one share to interpolate"); 43 | } 44 | let res: Vec> = (0..shares[0].y.len()) 45 | .map(|s| { 46 | let e: Fp = shares 47 | .iter() 48 | .map(|s_i| { 49 | let f: Fp = shares 50 | .iter() 51 | .filter(|s_j| s_j.x != s_i.x) 52 | .map(|s_j| s_j.x * (s_j.x - s_i.x).invert().unwrap()) 53 | .fold(Fp::ONE, |acc, x| acc * x); // take product of all fractions 54 | f * s_i.y[s] 55 | }) 56 | .fold(Fp::ZERO, |acc, x| acc + x); // take sum of all field elements 57 | Vec::from(e) // turn into byte vector 58 | }) 59 | .collect(); 60 | Ok( 61 | res 62 | .iter() 63 | .fold(Vec::new(), |acc, r| [acc, r.to_vec()].concat()), 64 | ) 65 | } 66 | 67 | // Generates `k` polynomial coefficients, being the last one `s` and the 68 | // others randomly generated in the field. 69 | // Coefficient degrees go from higher to lower in the returned vector 70 | // order. 71 | pub fn random_polynomial(s: Fp, k: u32, rng: &mut R) -> Vec { 72 | let k = k as usize; 73 | let mut poly = Vec::with_capacity(k); 74 | for _ in 1..k { 75 | poly.push(Fp::random(&mut *rng)); 76 | } 77 | poly.push(s); 78 | 79 | poly 80 | } 81 | 82 | // Returns an iterator over the points of the `polys` polynomials passed as argument. 83 | // Each item of the iterator is a tuple `(x, [f_1(x), f_2(x)..])` where eaxh `f_i` is the result for the ith polynomial. 84 | // Each polynomial corresponds to one byte chunk of the original secret. 85 | pub fn get_evaluator(polys: Vec>) -> Evaluator { 86 | Evaluator { polys, x: Fp::ZERO } 87 | } 88 | 89 | #[derive(Debug)] 90 | pub struct Evaluator { 91 | polys: Vec>, 92 | x: Fp, 93 | } 94 | 95 | impl Evaluator { 96 | fn evaluate(&self, x: Fp) -> Share { 97 | Share { 98 | x, 99 | y: self 100 | .polys 101 | .iter() 102 | .map(|p| p.iter().fold(Fp::ZERO, |acc, c| acc * x + c)) 103 | .collect(), 104 | } 105 | } 106 | 107 | pub fn gen(&self, rng: &mut R) -> Share { 108 | let rand = Fp::random(rng); 109 | self.evaluate(rand) 110 | } 111 | } 112 | 113 | // Implement `Iterator` for `Evaluator`. 114 | // The `Iterator` trait only requires a method to be defined for the `next` element. 115 | impl Iterator for Evaluator { 116 | type Item = Share; 117 | 118 | fn next(&mut self) -> Option { 119 | self.x += Fp::ONE; 120 | Some(self.evaluate(self.x)) 121 | } 122 | } 123 | 124 | /// A share used to reconstruct the secret. Can be serialized to and from a byte array. 125 | #[derive(Debug, Clone, Eq, PartialEq)] 126 | #[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] 127 | pub struct Share { 128 | pub x: Fp, 129 | pub y: Vec, 130 | } 131 | 132 | /// Obtains a byte vector from a `Share` instance 133 | impl From<&Share> for Vec { 134 | fn from(s: &Share) -> Vec { 135 | let mut bytes = Vec::with_capacity((s.y.len() + 1) * FIELD_ELEMENT_LEN); 136 | let repr = s.x.to_repr(); 137 | let x_coord = repr.as_ref().to_vec(); 138 | let y_coords = s 139 | .y 140 | .iter() 141 | .map(|p| p.to_repr().as_ref().to_vec()) 142 | .fold(Vec::new(), |acc, r| [acc, r.to_vec()].concat()); 143 | bytes.extend(x_coord); 144 | bytes.extend(y_coords); 145 | bytes 146 | } 147 | } 148 | 149 | /// Obtains a `Share` instance from a byte slice 150 | impl TryFrom<&[u8]> for Share { 151 | type Error = &'static str; 152 | 153 | fn try_from(s: &[u8]) -> Result { 154 | if s.len() < FIELD_ELEMENT_LEN { 155 | return Err( 156 | "A Share must have enough bytes to represent a field element", 157 | ); 158 | } 159 | let xr = FpRepr( 160 | s[..FIELD_ELEMENT_LEN] 161 | .try_into() 162 | // Slice into array only fails if the lengths don't match. 163 | // The length we pass is fixed, so this will not panic based 164 | // on the input data from the caller and unwrap is safe. 165 | .expect("byte slice should be the right size for an x coordinate"), 166 | ); 167 | let x = Option::from(Fp::from_repr(xr)) 168 | .ok_or("Failed to create field element from x representation")?; 169 | 170 | let y_bytes = &s[FIELD_ELEMENT_LEN..]; 171 | let y_count = y_bytes.len() / FIELD_ELEMENT_LEN; 172 | let mut y = Vec::with_capacity(y_count); 173 | for i in 0..y_count { 174 | let fr = FpRepr( 175 | y_bytes[i * FIELD_ELEMENT_LEN..(i + 1) * FIELD_ELEMENT_LEN] 176 | .try_into() 177 | .expect("byte slice should be the right size for a y coordinate"), 178 | ); 179 | let f = Option::from(Fp::from_repr(fr)) 180 | .ok_or("Failed to create field element from y representation")?; 181 | y.push(f); 182 | } 183 | Ok(Share { x, y }) 184 | } 185 | } 186 | 187 | /// Generate a Share from arbitrary input data. 188 | /// 189 | /// The derived `Arbitary` trait impl just splats data directly 190 | /// into the `Fp` struct without checking that it's a valid field 191 | /// element, which violates the invariants of the type and leads 192 | /// to false panic reports from the fuzzer. 193 | /// 194 | /// Implement the trait directly to ensure invalid values are 195 | /// not passed on to further code. 196 | #[cfg(feature = "fuzzing")] 197 | impl<'a> Arbitrary<'a> for Share { 198 | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { 199 | let count = u.arbitrary_len::()?; 200 | Share::try_from(u.bytes(count * FIELD_ELEMENT_LEN)?) 201 | .map_err(|_| arbitrary::Error::IncorrectFormat) 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use super::{get_evaluator, interpolate}; 208 | use super::{Fp, Share, FIELD_ELEMENT_LEN}; 209 | use crate::ff::Field; 210 | use alloc::{vec, vec::Vec}; 211 | use core::convert::TryFrom; 212 | use rand_chacha::rand_core::SeedableRng; 213 | 214 | fn fp_one() -> Fp { 215 | Fp::ONE 216 | } 217 | 218 | fn fp_two() -> Fp { 219 | fp_one().double() 220 | } 221 | 222 | fn fp_three() -> Fp { 223 | fp_two() + fp_one() 224 | } 225 | 226 | #[test] 227 | fn field_addition() { 228 | let x = fp_one(); 229 | let y = fp_two(); 230 | let z = fp_three(); 231 | assert_eq!(x + y, z); 232 | } 233 | 234 | #[test] 235 | fn field_mult() { 236 | let x = fp_three(); 237 | let y = fp_one(); 238 | let z = fp_three(); 239 | assert_eq!(Vec::from(x * y) as Vec, Vec::from(z) as Vec); 240 | } 241 | 242 | #[test] 243 | fn random_polynomial() { 244 | let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); 245 | let poly = super::random_polynomial(fp_one(), 3, &mut rng); 246 | assert_eq!(poly.len(), 3); 247 | assert_eq!(poly[2], fp_one()); 248 | } 249 | 250 | #[test] 251 | fn evaluation() { 252 | let iter = 253 | get_evaluator(vec![vec![fp_three(), fp_two(), fp_three() + fp_two()]]); 254 | let values: Vec<(Fp, Vec)> = iter.take(2).map(|s| (s.x, s.y)).collect(); 255 | assert_eq!( 256 | values, 257 | vec![ 258 | (fp_one(), vec![Fp([12451u64, 18446744073709427106, 0])]), 259 | (fp_two(), vec![Fp([12451u64, 18446744073709290145, 0])]) 260 | ] 261 | ); 262 | } 263 | 264 | #[test] 265 | fn interpolation() { 266 | let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); 267 | let poly = super::random_polynomial(fp_one(), 5, &mut rng); 268 | let iter = get_evaluator(vec![poly]); 269 | let shares: Vec = iter.take(5).collect(); 270 | let root = interpolate(&shares).unwrap(); 271 | let mut chk = vec![0u8; FIELD_ELEMENT_LEN]; 272 | chk[0] = 1u8; 273 | assert_eq!(root, chk); 274 | } 275 | 276 | #[test] 277 | fn vec_from_share() { 278 | let share = Share { 279 | x: fp_one(), 280 | y: vec![fp_two(), fp_three()], 281 | }; 282 | let bytes = Vec::from(&share); 283 | let chk_bytes = test_bytes(); 284 | assert_eq!(bytes, chk_bytes); 285 | } 286 | 287 | #[test] 288 | fn share_from_u8_slice() { 289 | let share = Share::try_from(&test_bytes()[..]).unwrap(); 290 | assert_eq!(share.x, fp_one()); 291 | assert_eq!(share.y, vec![fp_two(), fp_three()]); 292 | } 293 | 294 | #[test] 295 | fn share_from_u8_slice_without_y() { 296 | let share = Share::try_from(&test_bytes()[..FIELD_ELEMENT_LEN]).unwrap(); 297 | assert_eq!(share.x, fp_one()); 298 | assert_eq!(share.y, vec![]); 299 | } 300 | 301 | #[test] 302 | fn share_from_u8_slice_partial_y() { 303 | let share = 304 | Share::try_from(&test_bytes()[..FIELD_ELEMENT_LEN + 20]).unwrap(); 305 | assert_eq!(share.x, fp_one()); 306 | assert_eq!(share.y, vec![]); 307 | let share = 308 | Share::try_from(&test_bytes()[..FIELD_ELEMENT_LEN * 2 + 12]).unwrap(); 309 | assert_eq!(share.x, fp_one()); 310 | assert_eq!(share.y, vec![fp_two()]); 311 | } 312 | 313 | #[test] 314 | fn share_from_short_u8_slice() { 315 | let bytes = test_bytes(); 316 | assert!(Share::try_from(&bytes[0..FIELD_ELEMENT_LEN - 1]).is_err()); 317 | assert!(Share::try_from(&bytes[0..1]).is_err()); 318 | } 319 | 320 | fn test_bytes() -> Vec { 321 | let suffix = vec![0u8; FIELD_ELEMENT_LEN - 1]; 322 | let mut bytes = vec![1u8; 1]; 323 | bytes.extend(suffix.clone()); // x coord 324 | bytes.extend(vec![2u8; 1]); 325 | bytes.extend(suffix.clone()); // y coord #1 326 | bytes.extend(vec![3u8; 1]); 327 | bytes.extend(suffix); // y coord #2 328 | bytes 329 | } 330 | 331 | #[test] 332 | fn bad_share_bytes() { 333 | let bytes: Vec = vec![ 334 | 10u8, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 10, 0, 0, 0, 0, 0, 0, 335 | 10, 0, 0, 0, 0, 0, 336 | ]; 337 | let _ = Share::try_from(bytes.as_slice()); 338 | } 339 | 340 | #[test] 341 | fn element_length() { 342 | assert_eq!(FIELD_ELEMENT_LEN, core::mem::size_of::()); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /star-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | target 3 | -------------------------------------------------------------------------------- /star-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "star-wasm" 3 | version = "0.2.2" 4 | authors = ["Rémi Berson "] 5 | description = "WASM bindings for the STAR protocol" 6 | repository = "https://github.com/brave/sta-rs" 7 | keywords = ["crypto", "protocol", "privacy", "analytics", "wasm"] 8 | categories = ["cryptography", "algorithms", "webassembly"] 9 | license = "MPL-2.0" 10 | edition = "2018" 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | default = [] 17 | 18 | [dependencies] 19 | sta-rs = { path = "../star", default-features = false } 20 | getrandom = { version = "0.2", features = ["js"] } 21 | wasm-bindgen = "0.2" 22 | base64 = "0.22" 23 | console_error_panic_hook = "0.1.7" 24 | 25 | [dev-dependencies] 26 | wasm-bindgen-test = "0.3" 27 | -------------------------------------------------------------------------------- /star-wasm/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: build 3 | 4 | build: 5 | wasm-pack build --target bundler --release 6 | 7 | build-nodejs: 8 | wasm-pack build --target nodejs --release 9 | 10 | clean: 11 | cargo clean 12 | rm -frv pkg 13 | -------------------------------------------------------------------------------- /star-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! WebAssembly bindings for the STAR protocol 2 | //! 3 | //! This provides a threshold-aggregation scheme based on secret sharing, 4 | //! allowing data to be encrypted by clients in a way that a receiving 5 | //! server can only decrypt values submitted by multiple clients, offering 6 | //! safety-in-numbers privacy. 7 | 8 | use wasm_bindgen::prelude::*; 9 | 10 | use base64::{engine::Engine as _, prelude::BASE64_STANDARD}; 11 | 12 | use sta_rs::{ 13 | derive_ske_key, share_recover, MessageGenerator, Share, SingleMeasurement, 14 | WASMSharingMaterial, 15 | }; 16 | 17 | // NOTE - this can be used for debugging. Disabled for the production build. 18 | // extern crate console_error_panic_hook; 19 | // use std::panic; 20 | 21 | /// This function takes as input a secret (the `measurement`), a `threshold` (number of shares 22 | /// required to retrieve the encryption key and the initial secret on the server-side) and an 23 | /// `epoch` (used for versioning). 24 | /// 25 | /// It returns a triple (tag, share, key) where: 26 | /// - `tag` is used to group "compatible" shares on the server-side, this is used to get all the 27 | /// shares which were derived from the same secret. 28 | /// - `share` contains the secret (`key`) which can then be used server-side to retrive the 29 | /// decryption key and access the original metadata associated with a URL (only once the threshold 30 | /// has been met) 31 | /// - `key` is the encryption key which will be used by the client to encrypt the metadata before 32 | /// sending to the server. the `key` will *never* be sent to the server. 33 | /// 34 | /// What we send to the backend is a pair (share, encrypted_metadata). Which means that the server 35 | /// must have received at least `threshold` shares for a given secret to be able to recover the 36 | /// decryption key and decrypt `encrypted_metadata`. 37 | #[wasm_bindgen] 38 | pub fn create_share(measurement: &[u8], threshold: u32, epoch: &str) -> String { 39 | // NOTE - enable for debugging. 40 | // panic::set_hook(Box::new(console_error_panic_hook::hook)); 41 | 42 | let mg = MessageGenerator::new( 43 | SingleMeasurement::new(measurement), 44 | threshold, 45 | epoch.as_bytes(), 46 | ); 47 | let share_result = mg.share_with_local_randomness(); 48 | if share_result.is_err() { 49 | return "".to_owned(); 50 | } 51 | let WASMSharingMaterial { key, share, tag } = share_result.unwrap(); 52 | 53 | let key_b64 = BASE64_STANDARD.encode(key); 54 | let share_b64 = BASE64_STANDARD.encode(share.to_bytes()); 55 | let tag_b64 = BASE64_STANDARD.encode(tag); 56 | 57 | format!( 58 | r#"{{"key": "{key_b64}", "share": "{share_b64}", "tag": "{tag_b64}"}}"# 59 | ) 60 | } 61 | 62 | /// This function takes as argument a (serialized) list of shares (type: Share). The assumption is 63 | /// that the user of this function will already have grouped shares by `tag` and only calls the 64 | /// `group_shares` function if we have received more than `threshold` shares. 65 | #[wasm_bindgen] 66 | pub fn group_shares(serialized_shares: &str, epoch: &str) -> Option { 67 | // 1. deserialize shares into Vec 68 | let shares: Vec = serialized_shares 69 | .split('\n') 70 | .map(|chunk| { 71 | Share::from_bytes(&BASE64_STANDARD.decode(chunk).unwrap()).unwrap() 72 | }) 73 | .collect(); 74 | 75 | // 2. call recover(shares) 76 | let res = share_recover(&shares); 77 | if res.is_err() { 78 | return None; 79 | } 80 | let message = res.unwrap().get_message(); 81 | 82 | // 3. call derive_ske_key 83 | let mut enc_key = vec![0u8; 16]; 84 | derive_ske_key(&message, epoch.as_bytes(), &mut enc_key); 85 | 86 | Some(BASE64_STANDARD.encode(&enc_key)) 87 | } 88 | -------------------------------------------------------------------------------- /star-wasm/www/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: "eslint:recommended", 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: "module", 11 | }, 12 | rules: {}, 13 | }; 14 | -------------------------------------------------------------------------------- /star-wasm/www/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /star-wasm/www/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: build 3 | 4 | release: node_modules/webpack 5 | rm -frv dist 6 | NODE_ENV=production npm run build:release 7 | NODE_ENV=production npm ci 8 | 9 | build: node_modules/webpack 10 | npm run build 11 | 12 | node_modules/webpack: 13 | npm install 14 | 15 | clean: 16 | rm -frv dist 17 | rm -fr node_modules 18 | -------------------------------------------------------------------------------- /star-wasm/www/README.md: -------------------------------------------------------------------------------- 1 | STAR in the browser with WebAssembly! 2 | 3 | ## Running 4 | 5 | ```sh 6 | make build -C ../wasm && npm install && make release && node server.js 7 | ``` 8 | 9 | Then visit: `http://localhost:8084`. 10 | 11 | Open dev tools and check `console`. 12 | -------------------------------------------------------------------------------- /star-wasm/www/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.js").catch((e) => 5 | console.error("Error importing `index.js`:", e) 6 | ); 7 | -------------------------------------------------------------------------------- /star-wasm/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | STAR 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /star-wasm/www/index.js: -------------------------------------------------------------------------------- 1 | import { create_share, group_shares } from "star-wasm"; 2 | 3 | /* Encodes a Uint8Array as a base64 string */ 4 | function toBase64Fast(data) { 5 | return btoa(_toString(toByteArray(data))); 6 | } 7 | 8 | /* Decodes a base64 string as a Uint8Array */ 9 | function fromBase64Fast(data) { 10 | return _fromString(atob(data)); 11 | } 12 | 13 | function toByteArray(data) { 14 | if (data.buffer) { 15 | return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 16 | } 17 | return new Uint8Array(data); 18 | } 19 | 20 | function _toString(data) { 21 | const CHUNK_SIZE = 16383; // 32767 is too much for MS Edge in 2 Gb virtual machine 22 | const c = []; 23 | const len = data.length; 24 | for (let i = 0; i < len; i += CHUNK_SIZE) { 25 | c.push(String.fromCharCode.apply(null, data.subarray(i, i + CHUNK_SIZE))); 26 | } 27 | return c.join(""); 28 | } 29 | 30 | function _fromString(data) { 31 | const res = new Uint8Array(data.length); 32 | const len = data.length; 33 | for (let i = 0; i < len; i += 1) { 34 | res[i] = data.charCodeAt(i); 35 | } 36 | return res; 37 | } 38 | 39 | // http://ecmanaut.blogspot.de/2006/07/encoding-decoding-utf8-in-javascript.html 40 | function _toUTF8(s) { 41 | return _fromString(unescape(encodeURIComponent(s))); 42 | } 43 | 44 | function _fromUTF8(s) { 45 | return decodeURIComponent(escape(_toString(s))); 46 | } 47 | 48 | /* Returns a string given a Uint8Array UTF-8 encoding */ 49 | const decoder = TextDecoder ? new TextDecoder() : { decode: _fromUTF8 }; 50 | function fromUTF8(bytes) { 51 | return decoder.decode(toByteArray(bytes)); 52 | } 53 | 54 | /* Returns a Uint8Array UTF-8 encoding of the given string */ 55 | const encoder = TextEncoder ? new TextEncoder() : { encode: _toUTF8 }; 56 | function toUTF8(str) { 57 | return encoder.encode(str); 58 | } 59 | 60 | const EPOCH = "1"; 61 | const THRESHOLD = 2; 62 | 63 | /** 64 | * Given a `url` and `page` message to send to the backend, create a random 65 | * STAR share and wrap it into a JSON message which can be sent to the backend. 66 | * The backend then collects all shares and decrypts the ones which have reached 67 | * the threshold. 68 | */ 69 | async function prepareMessage(url, page) { 70 | // `tag`, `key` and `share` are base64-encoded strings. 71 | const t0 = Date.now(); 72 | const { tag, key, share } = JSON.parse( 73 | create_share(_fromString(url), THRESHOLD, EPOCH) 74 | ); 75 | const t1 = Date.now(); 76 | console.log("create_share:", t1 - t0); 77 | 78 | // Prepare page message to be encrypted -> Uint8Array 79 | // NOTE: eventually this will be the HPN-encrypted payload instead. 80 | const payload = toUTF8(JSON.stringify(page)); 81 | 82 | // importKey: get actual encryption key from `key` 83 | const aesKey = await window.crypto.subtle.importKey( 84 | "raw", 85 | fromBase64Fast(key), 86 | { 87 | name: "AES-GCM", 88 | }, 89 | false, 90 | ["encrypt"] 91 | ); 92 | 93 | // Generate random `iv` 94 | const iv = window.crypto.getRandomValues(new Uint8Array(12)); 95 | 96 | // Encrypt `payload` with `aesKey` 97 | const encrypted = await window.crypto.subtle.encrypt( 98 | { 99 | name: "AES-GCM", 100 | iv, 101 | }, 102 | aesKey, 103 | payload 104 | ); 105 | 106 | return { 107 | // Constants: backend will need them to stay in sync with clients. 108 | // NOTE: the value of `tag` should be sensitive to both of these values, 109 | // which means that changing `EPOCH` or `THRESHOLD` will result in a 110 | // different tag. It should thus be safe for the backend to group by `tag` 111 | // and expect that values of all constants will match. 112 | threshold: THRESHOLD, 113 | epoch: EPOCH, 114 | 115 | // Information about the STAR share 116 | tag, 117 | share, 118 | 119 | // Information about encrypted accompanying data (i.e. page message) 120 | encrypted: toBase64Fast(encrypted), 121 | iv: toBase64Fast(iv), 122 | }; 123 | } 124 | 125 | async function recoverEncryptionKey(messages) { 126 | const epoch = messages[0].epoch; 127 | const t0 = Date.now(); 128 | const key = group_shares( 129 | messages.map(({ share }) => share).join("\n"), 130 | epoch 131 | ); 132 | const t1 = Date.now(); 133 | console.log("group_shares:", t1 - t0); 134 | 135 | return await window.crypto.subtle.importKey( 136 | "raw", 137 | fromBase64Fast(key), 138 | { 139 | name: "AES-GCM", 140 | }, 141 | false, 142 | ["decrypt"] 143 | ); 144 | } 145 | 146 | async function recoverPageMessages(messages, aesKey) { 147 | const decrypted = []; 148 | 149 | for (const { encrypted, iv } of messages) { 150 | decrypted.push( 151 | JSON.parse( 152 | fromUTF8( 153 | await window.crypto.subtle.decrypt( 154 | { 155 | name: "AES-GCM", 156 | iv: fromBase64Fast(iv), 157 | }, 158 | aesKey, 159 | fromBase64Fast(encrypted) 160 | ) 161 | ) 162 | ) 163 | ); 164 | } 165 | 166 | return decrypted; 167 | } 168 | 169 | async function processMessages(messages) { 170 | // Group all messages per tag 171 | const grouped = new Map(); 172 | for (const message of messages) { 173 | let bucket = grouped.get(message.tag); 174 | if (bucket === undefined) { 175 | bucket = []; 176 | grouped.set(message.tag, bucket); 177 | } 178 | bucket.push(message); 179 | } 180 | 181 | // Load data from groups which have reached threshold. 182 | for (const bucket of grouped.values()) { 183 | const threshold = bucket[0].threshold; 184 | if (bucket.length >= threshold) { 185 | // 1. Retrieve encryption key. 186 | const aesKey = await recoverEncryptionKey(bucket); 187 | 188 | // 2. Decrypt all messages. 189 | const pages = await recoverPageMessages(bucket, aesKey); 190 | 191 | console.error("Got pages", pages); 192 | } 193 | } 194 | } 195 | 196 | (async () => { 197 | const messages = []; 198 | 199 | // Will not be decrypted! 1 share 200 | messages.push( 201 | await prepareMessage("https://brave.com/internal", { 202 | url: "https://brave.com/internal", 203 | x: 0, 204 | }) 205 | ); 206 | 207 | // Ok! 2 shares 208 | messages.push( 209 | await prepareMessage("https://brave.com", { 210 | url: "https://brave.com", 211 | x: 1, 212 | }) 213 | ); 214 | messages.push( 215 | await prepareMessage("https://brave.com", { 216 | url: "https://brave.com", 217 | x: 2, 218 | }) 219 | ); 220 | 221 | // Ok! 3 shares 222 | messages.push( 223 | await prepareMessage("https://github.com", { 224 | url: "https://github.com", 225 | x: 0, 226 | }) 227 | ); 228 | messages.push( 229 | await prepareMessage("https://github.com", { 230 | url: "https://github.com", 231 | x: 1, 232 | }) 233 | ); 234 | messages.push( 235 | await prepareMessage("https://github.com", { 236 | url: "https://github.com", 237 | x: 2, 238 | }) 239 | ); 240 | 241 | processMessages(messages); 242 | })(); 243 | -------------------------------------------------------------------------------- /star-wasm/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star", 3 | "version": "0.1.0", 4 | "description": "Browser demo for STAR with WebAssembly", 5 | "scripts": { 6 | "build": "webpack --config webpack.config.js", 7 | "build:release": "webpack --config webpack.config.js --mode production" 8 | }, 9 | "author": "remi@brave.com", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/brave/sta-rs.git" 13 | }, 14 | "keywords": [ 15 | "webassembly", 16 | "wasm", 17 | "rust", 18 | "webpack", 19 | "star" 20 | ], 21 | "bugs": { 22 | "url": "https://github.com/brave/sta-rs/issues" 23 | }, 24 | "homepage": "https://github.com/brave/sta-rs#readme", 25 | "dependencies": { 26 | "koa": "3.0.3", 27 | "koa-static": "5.0.0", 28 | "star-wasm": "file:../pkg" 29 | }, 30 | "devDependencies": { 31 | "copy-webpack-plugin": "12.0.2", 32 | "eslint": "8.57.1", 33 | "webpack": "5.102.0", 34 | "webpack-cli": "5.1.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /star-wasm/www/server.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const serve = require('koa-static'); 3 | 4 | (() => { 5 | const host = '0.0.0.0'; 6 | const port = '8084'; 7 | const app = new Koa(); 8 | app.use(serve('./dist')); 9 | 10 | console.log( 11 | `Serving on http://${host}:${port}`, 12 | ); 13 | app.listen(port, host); 14 | })(); 15 | -------------------------------------------------------------------------------- /star-wasm/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "dist"), 8 | filename: "bootstrap.js", 9 | }, 10 | mode: "development", 11 | plugins: [ 12 | new CopyWebpackPlugin({ 13 | patterns: [ 14 | { from: "index.html" }, 15 | ], 16 | }), 17 | ], 18 | experiments: { 19 | asyncWebAssembly: true 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /star/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sta-rs" 3 | version = "0.3.2" 4 | authors = ["Alex Davidson "] 5 | description = "Distributed Secret-Sharing for Threshold Aggregation Reporting" 6 | documentation = "https://docs.rs/sta-rs" 7 | repository = "https://github.com/brave/sta-rs" 8 | keywords = ["crypto", "protocol", "privacy", "secret", "analytics"] 9 | categories = ["cryptography", "algorithms"] 10 | license = "MPL-2.0" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | strobe-rs = "0.10.0" 15 | adss = { path = "../adss", version = "0.2.3" } 16 | ppoprf = { path = "../ppoprf", version = "0.4.2" } 17 | rand = "0.8.5" 18 | rand_core = "0.6.4" 19 | zeroize = "1.5.5" 20 | 21 | [dev-dependencies] 22 | criterion = "0.5.1" 23 | star-test-utils = { path = "./test-utils" } 24 | rand = { version = "0.8.5", features = [ "std" ] } 25 | 26 | [features] 27 | star2 = [] 28 | 29 | [[bench]] 30 | name = "bench" 31 | harness = false 32 | -------------------------------------------------------------------------------- /star/README.md: -------------------------------------------------------------------------------- 1 | # sta-rs crate 2 | 3 | Rust implementation of the [STAR 4 | protocol](https://arxiv.org/abs/2109.10074). Consists of client API 5 | functions for generating STAR messages, and server aggregation 6 | functions. 7 | 8 | ## Disclaimer 9 | 10 | WARNING the libraries present in this workspace have not been audited, 11 | use at your own risk! This code is under active development and may 12 | change substantially in future versions. 13 | 14 | ## Quickstart 15 | 16 | Build & test: 17 | ``` 18 | cargo build 19 | cargo test 20 | ``` 21 | 22 | Benchmarks: 23 | ``` 24 | cargo bench 25 | ``` 26 | 27 | Open local copy of documentation: 28 | ``` 29 | cargo doc --open --no-deps 30 | ``` 31 | -------------------------------------------------------------------------------- /star/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The code in this repository is still being actively developed, and is thus regarded as suitable only for experimental purposes. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any observed issues to the [GitHub repository](https://github.com/brave/sta-rs/). 10 | -------------------------------------------------------------------------------- /star/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use core::iter; 2 | use criterion::{ 3 | criterion_group, criterion_main, Criterion, PlotConfiguration, 4 | }; 5 | use rand::Rng; 6 | 7 | #[cfg(feature = "star2")] 8 | use ppoprf::ppoprf::Server as PPOPRFServer; 9 | use sta_rs::{AssociatedData, Message}; 10 | use star_test_utils::*; 11 | 12 | fn criterion_benchmark(c: &mut Criterion) { 13 | benchmark_client_randomness_sampling(c); 14 | benchmark_client_triple_generation(c); 15 | benchmark_server_retrieval(c); 16 | benchmark_end_to_end(c); 17 | } 18 | 19 | fn benchmark_client_randomness_sampling(c: &mut Criterion) { 20 | c.bench_function("Client local randomness", |b| { 21 | let client = client_zipf(10000, 1.03, 2, b"t"); 22 | let mut out = vec![0u8; 32]; 23 | b.iter(|| { 24 | client.sample_local_randomness(&mut out); 25 | }); 26 | }); 27 | 28 | #[cfg(feature = "star2")] 29 | c.bench_function("Client ppoprf randomness", |b| { 30 | let client = client_zipf(10000, 1.03, 2, b"t"); 31 | let ppoprf_server = PPOPRFServer::new(&[b"t".to_vec()]); 32 | let mut out = vec![0u8; 32]; 33 | b.iter(|| { 34 | client.sample_oprf_randomness(&ppoprf_server, &mut out); 35 | }); 36 | }); 37 | } 38 | 39 | fn benchmark_client_triple_generation(c: &mut Criterion) { 40 | c.bench_function("Client generate triple (local)", |b| { 41 | let mg = client_zipf(10000, 1.03, 2, b"t"); 42 | let mut rnd = [0u8; 32]; 43 | mg.sample_local_randomness(&mut rnd); 44 | b.iter(|| Message::generate(&mg, &rnd, None).unwrap()); 45 | }); 46 | 47 | #[cfg(feature = "star2")] 48 | c.bench_function("Client generate triple (ppoprf)", |b| { 49 | let mg = client_zipf(10000, 1.03, 2, b"t"); 50 | let ppoprf_server = PPOPRFServer::new(&[b"t".to_vec()]); 51 | let mut rnd = [0u8; 32]; 52 | mg.sample_oprf_randomness(ppoprf_server, &mut rnd); 53 | b.iter(|| { 54 | Message::generate(&mg, &rnd, None).unwrap(); 55 | }); 56 | }); 57 | 58 | c.bench_function("Client generate triple (local, aux)", |b| { 59 | let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 60 | let mg = client_zipf(10000, 1.03, 2, b"t"); 61 | let mut rnd = [0u8; 32]; 62 | mg.sample_local_randomness(&mut rnd); 63 | b.iter(|| { 64 | Message::generate(&mg, &rnd, Some(AssociatedData::new(&random_bytes))) 65 | .unwrap(); 66 | }); 67 | }); 68 | 69 | #[cfg(feature = "star2")] 70 | c.bench_function("Client generate triple (ppoprf, aux)", |b| { 71 | let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 72 | let mg = client_zipf(10000, 1.03, 2, b"t"); 73 | let ppoprf_server = PPOPRFServer::new(&[b"t".to_vec()]); 74 | let mut rnd = [0u8; 32]; 75 | mg.sample_oprf_randomness(ppoprf_server, &mut rnd); 76 | b.iter(|| { 77 | Message::generate(&mg, &rnd, Some(AssociatedData::new(&random_bytes))) 78 | .unwrap(); 79 | }); 80 | }); 81 | } 82 | 83 | fn benchmark_server_retrieval(c: &mut Criterion) { 84 | let mg = client_zipf(10000, 1.03, 50, b"t"); 85 | let mut rnd = [0u8; 32]; 86 | mg.sample_local_randomness(&mut rnd); 87 | let messages: Vec = 88 | iter::repeat_with(|| Message::generate(&mg, &rnd, None).unwrap()) 89 | .take(1000) 90 | .collect(); 91 | c.bench_function("Server retrieve outputs", |b| { 92 | let agg_server = AggregationServer::new(50, "t"); 93 | b.iter(|| { 94 | let _o = agg_server.retrieve_outputs(&messages); 95 | }); 96 | }); 97 | } 98 | 99 | struct Params { 100 | n: usize, 101 | s: f64, 102 | clients: usize, 103 | threshold: u32, 104 | local: bool, 105 | aux_data: bool, 106 | } 107 | 108 | fn benchmark_end_to_end(c: &mut Criterion) { 109 | let plot_config = PlotConfiguration::default(); 110 | let mut group = c.benchmark_group("end-to-end"); 111 | group.plot_config(plot_config); 112 | group.sample_size(10); 113 | [ 114 | Params { n: 10000, s: 1.03, clients: 100000, threshold: 100, local: true, aux_data: false }, 115 | Params { n: 10000, s: 1.03, clients: 250000, threshold: 250, local: true, aux_data: false }, 116 | Params { n: 10000, s: 1.03, clients: 500000, threshold: 500, local: true, aux_data: false }, 117 | Params { n: 10000, s: 1.03, clients: 1000000, threshold: 1000, local: true, aux_data: false }, 118 | ].iter().for_each(|params| { 119 | let epoch = "t"; 120 | let messages = get_messages(params, epoch); 121 | group.bench_function(format!("E2E server (n={}, s={}, clients={}, threshold={}, local_randomness={}, aux_data={})", params.n, params.s, params.clients, params.threshold, params.local, params.aux_data), |b| { 122 | let agg_server = AggregationServer::new(params.threshold, epoch); 123 | b.iter(|| { 124 | let _o = agg_server.retrieve_outputs(&messages); 125 | }); 126 | }); 127 | }); 128 | } 129 | 130 | fn get_messages(params: &Params, epoch: &str) -> Vec { 131 | let mg = client_zipf(params.n, params.s, params.threshold, epoch.as_bytes()); 132 | let mut rnd = [0u8; 32]; 133 | if params.local { 134 | iter::repeat_with(|| { 135 | Message::generate(&mg, &rnd, get_aux_data(params.aux_data)).unwrap() 136 | }) 137 | .take(params.clients) 138 | .collect() 139 | } else { 140 | mg.sample_local_randomness(&mut rnd); 141 | #[cfg(not(feature = "star2"))] 142 | unimplemented!(); 143 | #[cfg(feature = "star2")] 144 | { 145 | let mut ppoprf_server = PPOPRFServer::new(&[b"t".to_vec()]); 146 | let messages = iter::repeat_with(|| { 147 | sample_oprf_randomness(&ppoprf_server, &mut rnd); 148 | Message::generate(&mg, &rnd, get_aux_data(params.aux_data)).unwrap() 149 | }) 150 | .take(params.clients) 151 | .collect(); 152 | ppoprf_server.puncture(epoch.as_bytes()); 153 | messages 154 | } 155 | } 156 | } 157 | 158 | fn get_aux_data(do_it: bool) -> Option { 159 | if do_it { 160 | return Some(AssociatedData::new(&rand::thread_rng().gen::<[u8; 8]>())); 161 | } 162 | None 163 | } 164 | 165 | criterion_group!(benches, criterion_benchmark); 166 | criterion_main!(benches); 167 | -------------------------------------------------------------------------------- /star/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the implementation of the STAR (distributed 2 | //! Secret-sharing for Threshold AggRegation of data) protocol. The STAR 3 | //! protocol provides the ability for clients to report secret 4 | //! measurements to servers, whilst maintaining k-anonymity-like 5 | //! guarantees. 6 | //! 7 | //! In essence, such measurements are only revealed if a `threshold` 8 | //! number of clients all send the same message. Clients are permitted 9 | //! to also send relevant, arbitrary associated data that can also be 10 | //! revealed. 11 | //! 12 | //! In STAR, clients derive randomness from a separate server that 13 | //! implements a puncturable partially oblivious pseudorandom function 14 | //! (PPOPRF) protocol. In STARLite, clients derive randomness used for 15 | //! hiding their measurements locally from the measurement itself. The 16 | //! PPOPRF protocol takes in the client measurement, a server secret 17 | //! key, and the current epoch metadata tag as input, and outputs a 18 | //! random (deterministic) value. 19 | //! 20 | //! In the case of STARLite, the design is simpler than in STAR, but 21 | //! security is ONLY maintained in the case where client measurements 22 | //! are sampled from a high-entropy domain. In the case of STAR, client 23 | //! security guarantees hold even for low-entropy inputs, as long as the 24 | //! randomness is only revealed after the epoch metadata tag has been 25 | //! punctured from the randomness server's secret key. 26 | //! 27 | //! See the [full paper](https://arxiv.org/abs/2109.10074) for more 28 | //! details. 29 | //! 30 | //! # Example (client) 31 | //! 32 | //! The following example shows how to generate a message triple of `(ciphertext, 33 | //! share, tag)`. This message can then be sent to the aggregation server. 34 | //! 35 | //! ``` 36 | //! # use sta_rs::*; 37 | //! # let threshold = 2; 38 | //! # let epoch = "t"; 39 | //! let measurement = SingleMeasurement::new("hello world".as_bytes()); 40 | //! let mg = MessageGenerator::new(measurement, threshold, epoch.as_bytes()); 41 | //! let mut rnd = [0u8; 32]; 42 | //! // NOTE: this is for STARLite. Randomness must be sampled from a 43 | //! // randomness server in order to implement the full STAR protocol. 44 | //! mg.sample_local_randomness(&mut rnd); 45 | //! 46 | //! let Message { 47 | //! ciphertext, 48 | //! share, 49 | //! tag, 50 | //! } = Message::generate(&mg, &mut rnd, None) 51 | //! .expect("Could not generate message triplet"); 52 | //! ``` 53 | //! # Example (WASM client) 54 | //! 55 | //! The following example shows how to generate a triple of `(key, 56 | //! share, tag)` for each client in the STARLite protocol, which is used 57 | //! in the existing WASM integration. The STAR protocol is not yet 58 | //! supported. 59 | //! 60 | //! In the WASM integration the `key` MUST then be used to encrypt the 61 | //! measurement and associated data into a `ciphertext` in the 62 | //! higher-level application. The message triple `(ciphertext, share, 63 | //! tag)` is then sent to the server. 64 | //! 65 | //! ``` 66 | //! # use sta_rs::*; 67 | //! # let threshold = 2; 68 | //! # let epoch = "t"; 69 | //! let measurement = SingleMeasurement::new("hello world".as_bytes()); 70 | //! let mg = MessageGenerator::new(measurement, threshold, epoch.as_bytes()); 71 | //! let mut rnd = [0u8; 32]; 72 | //! // NOTE: this is for STARLite. Randomness must be sampled from a 73 | //! // randomness server in order to implement the full STAR protocol. 74 | //! mg.sample_local_randomness(&mut rnd); 75 | //! let WASMSharingMaterial { 76 | //! key, 77 | //! share, 78 | //! tag, 79 | //! } = mg.share_with_local_randomness().unwrap(); 80 | //! ``` 81 | //! 82 | //! # Example (server) 83 | //! 84 | //! Once over `threshold` shares are recovered from clients, it is 85 | //! possible to recover the randomness encoded in each of the shares 86 | //! 87 | //! ``` 88 | //! # use sta_rs::*; 89 | //! # use star_test_utils::*; 90 | //! # let mut messages = Vec::new(); 91 | //! # let threshold = 2; 92 | //! # let epoch = "t"; 93 | //! # let measurement = SingleMeasurement::new("hello world".as_bytes()); 94 | //! 95 | //! # let mg = MessageGenerator::new(measurement, threshold, epoch.as_bytes()); 96 | //! # for i in 0..3 { 97 | //! # let mut rnd = [0u8; 32]; 98 | //! # mg.sample_local_randomness(&mut rnd); 99 | //! # messages.push(Message::generate(&mg, &mut rnd, None).unwrap()); 100 | //! # } 101 | //! # let shares: Vec = messages.iter().map(|triple| triple.share.clone()).collect(); 102 | //! let value = share_recover(&shares).unwrap().get_message(); 103 | //! 104 | //! // derive key for decrypting payload data in client message 105 | //! let mut enc_key = vec![0u8; 16]; 106 | //! derive_ske_key(&value, epoch.as_bytes(), &mut enc_key); 107 | //! ``` 108 | use std::error::Error; 109 | use std::str; 110 | 111 | use rand::Rng; 112 | mod strobe_rng; 113 | use strobe_rng::StrobeRng; 114 | use strobe_rs::{SecParam, Strobe}; 115 | use zeroize::{Zeroize, ZeroizeOnDrop}; 116 | 117 | use adss::{recover, Commune}; 118 | pub use {adss::load_bytes, adss::store_bytes, adss::Share as InternalShare}; 119 | 120 | #[cfg(feature = "star2")] 121 | use ppoprf::ppoprf::{end_to_end_evaluation, Server as PPOPRFServer}; 122 | 123 | pub const AES_BLOCK_LEN: usize = 24; 124 | pub const DIGEST_LEN: usize = 32; 125 | 126 | // A `Measurement` provides the wrapper for a client-generated value in 127 | // the STAR protocol that is later aggregated and processed at the 128 | // server-side. Measurements are only revealed on the server-side if the 129 | // `threshold` is met, in terms of clients that send the same 130 | // `Measurement` value. 131 | #[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] 132 | pub struct SingleMeasurement(Vec); 133 | impl SingleMeasurement { 134 | pub fn new(x: &[u8]) -> Self { 135 | Self(x.to_vec()) 136 | } 137 | 138 | pub fn as_slice(&self) -> &[u8] { 139 | self.0.as_slice() 140 | } 141 | 142 | pub fn as_vec(&self) -> Vec { 143 | self.0.clone() 144 | } 145 | 146 | pub fn byte_len(&self) -> usize { 147 | self.0.len() 148 | } 149 | 150 | pub fn is_empty(&self) -> bool { 151 | self.0.is_empty() 152 | } 153 | } 154 | 155 | impl From<&str> for SingleMeasurement { 156 | fn from(s: &str) -> Self { 157 | SingleMeasurement::new(s.as_bytes()) 158 | } 159 | } 160 | 161 | // The `AssociatedData` struct wraps the arbitrary data that a client 162 | // can encode in its message to the `Server`. Such data is also only 163 | // revealed in the case that the `threshold` is met. 164 | #[derive(Debug)] 165 | pub struct AssociatedData(Vec); 166 | impl AssociatedData { 167 | pub fn new(buf: &[u8]) -> Self { 168 | Self(buf.to_vec()) 169 | } 170 | 171 | pub fn as_slice(&self) -> &[u8] { 172 | self.0.as_slice() 173 | } 174 | 175 | pub fn as_vec(&self) -> Vec { 176 | self.0.clone() 177 | } 178 | } 179 | impl From<&str> for AssociatedData { 180 | fn from(s: &str) -> Self { 181 | AssociatedData::from(s.as_bytes()) 182 | } 183 | } 184 | impl From<&[u8]> for AssociatedData { 185 | fn from(buf: &[u8]) -> Self { 186 | AssociatedData::new(buf) 187 | } 188 | } 189 | 190 | // Wrapper type for `adss::Share` to implement `ZeroizeOnDrop`properly. 191 | #[derive(Clone, Debug, PartialEq, Eq, Zeroize)] 192 | pub struct Share(InternalShare); 193 | impl Share { 194 | pub fn to_bytes(&self) -> Vec { 195 | self.0.to_bytes() 196 | } 197 | 198 | pub fn from_bytes(bytes: &[u8]) -> Option { 199 | Some(Self(InternalShare::from_bytes(bytes)?)) 200 | } 201 | } 202 | impl Drop for Share { 203 | fn drop(&mut self) { 204 | self.0.zeroize(); 205 | } 206 | } 207 | 208 | // The `Ciphertext` struct holds the symmetrically encrypted data that 209 | // corresponds to the concatenation of `Measurement` and any optional 210 | // `AssociatedData`. 211 | #[derive(Debug, Clone, PartialEq, Eq)] 212 | pub struct Ciphertext { 213 | bytes: Vec, 214 | } 215 | impl Ciphertext { 216 | pub fn new(enc_key_buf: &[u8], data: &[u8], label: &str) -> Self { 217 | let mut s = Strobe::new(label.as_bytes(), SecParam::B128); 218 | s.key(enc_key_buf, false); 219 | let mut x = vec![0u8; data.len()]; 220 | x.copy_from_slice(data); 221 | s.send_enc(&mut x, false); 222 | 223 | Self { bytes: x.to_vec() } 224 | } 225 | 226 | pub fn decrypt(&self, enc_key_buf: &[u8], label: &str) -> Vec { 227 | let mut s = Strobe::new(label.as_bytes(), SecParam::B128); 228 | s.key(enc_key_buf, false); 229 | let mut m = vec![0u8; self.bytes.len()]; 230 | m.copy_from_slice(&self.bytes); 231 | s.recv_enc(&mut m, false); 232 | m 233 | } 234 | 235 | pub fn to_bytes(&self) -> Vec { 236 | self.bytes.clone() 237 | } 238 | 239 | pub fn from_bytes(bytes: &[u8]) -> Ciphertext { 240 | Self { 241 | bytes: bytes.to_vec(), 242 | } 243 | } 244 | } 245 | impl From> for Ciphertext { 246 | fn from(bytes: Vec) -> Self { 247 | Self { bytes } 248 | } 249 | } 250 | 251 | // A `Message` is the message that a client sends to the server during 252 | // the STAR protocol. Consisting of a `Ciphertext`, a `Share`, and a 253 | // `tag`. The `Ciphertext`can only be decrypted if a `threshold` number 254 | // of clients possess the same measurement. 255 | // 256 | // This struct should only be used by applications that do not perform 257 | // encryption at the higher application-levels. 258 | #[derive(Clone, Debug, PartialEq, Eq)] 259 | pub struct Message { 260 | pub ciphertext: Ciphertext, 261 | pub share: Share, 262 | pub tag: Vec, 263 | } 264 | impl Message { 265 | fn new(c: Ciphertext, share: Share, tag: &[u8]) -> Self { 266 | Self { 267 | ciphertext: c, 268 | share, 269 | tag: tag.to_vec(), 270 | } 271 | } 272 | 273 | // Generates a message that is used in the aggregation phase 274 | pub fn generate( 275 | mg: &MessageGenerator, 276 | rnd: &[u8; 32], 277 | aux: Option, 278 | ) -> Result> { 279 | let r = mg.derive_random_values(rnd); 280 | 281 | // key is then used for encrypting measurement and associated 282 | // data 283 | let key = mg.derive_key(&r[0]); 284 | let share = mg.share(&r[0], &r[1])?; 285 | let tag = r[2]; 286 | 287 | let mut data: Vec = Vec::new(); 288 | store_bytes(mg.x.as_slice(), &mut data); 289 | if let Some(ad) = aux { 290 | store_bytes(ad.as_slice(), &mut data); 291 | } 292 | let ciphertext = Ciphertext::new(&key, &data, "star_encrypt"); 293 | 294 | Ok(Message::new(ciphertext, share, &tag)) 295 | } 296 | 297 | pub fn to_bytes(&self) -> Vec { 298 | let mut out: Vec = Vec::new(); 299 | 300 | // ciphertext: Ciphertext 301 | store_bytes(&self.ciphertext.to_bytes(), &mut out); 302 | 303 | // share: Share 304 | store_bytes(&self.share.to_bytes(), &mut out); 305 | 306 | // tag: Vec 307 | store_bytes(&self.tag, &mut out); 308 | 309 | out 310 | } 311 | 312 | pub fn from_bytes(bytes: &[u8]) -> Option { 313 | let mut slice = bytes; 314 | 315 | // ciphertext: Ciphertext 316 | let cb = load_bytes(slice)?; 317 | let ciphertext = Ciphertext::from_bytes(cb); 318 | slice = &slice[4 + cb.len()..]; 319 | 320 | // share: Share 321 | let sb = load_bytes(slice)?; 322 | let share = Share::from_bytes(sb)?; 323 | slice = &slice[4 + sb.len()..]; 324 | 325 | // tag: Vec 326 | let tag = load_bytes(slice)?; 327 | 328 | Some(Message { 329 | ciphertext, 330 | share, 331 | tag: tag.to_vec(), 332 | }) 333 | } 334 | } 335 | 336 | // The `WASMSharingMaterial` consists of all data that is passed to 337 | // higher-level applications using the star-wasm API. This allows 338 | // encrypting and sending the client measurements in higher-level 339 | // implementations of the STAR protocol. 340 | #[derive(Zeroize)] 341 | pub struct WASMSharingMaterial { 342 | /// 16-byte AES encryption key 343 | pub key: [u8; 16], 344 | /// Secret share of key derivation randomness 345 | pub share: Share, 346 | /// 32-byte random tag associated with client measurement 347 | pub tag: [u8; 32], 348 | } 349 | 350 | // In the STAR protocol, the `MessageGenerator` is the entity which 351 | // samples and sends `Measurement` to the `AggregationServer`. The 352 | // measurements will only be revealed if a `threshold` number of 353 | // MessageGenerators send the same encoded `Measurement` value. 354 | // 355 | // Note that the `MessageGenerator` struct holds all of the public 356 | // protocol parameters, the secret `Measurement` and `AssociatedData` 357 | // objects, and where randomness should be sampled from. 358 | // 359 | // In the STARLite protocol, the `MessageGenerator` samples randomness 360 | // locally: derived straight from the `Measurement` itself. In the STAR 361 | // protocol, the `MessageGenerator` derives its randomness from an 362 | // exchange with a specifically-defined server that runs a POPRF. 363 | #[derive(Zeroize, ZeroizeOnDrop)] 364 | pub struct MessageGenerator { 365 | pub x: SingleMeasurement, 366 | threshold: u32, 367 | epoch: Vec, 368 | } 369 | impl MessageGenerator { 370 | pub fn new(x: SingleMeasurement, threshold: u32, epoch: &[u8]) -> Self { 371 | Self { 372 | x, 373 | threshold, 374 | epoch: epoch.into(), 375 | } 376 | } 377 | 378 | // Share with OPRF randomness (STARLite) 379 | pub fn share_with_local_randomness( 380 | &self, 381 | ) -> Result> { 382 | let mut rnd = vec![0u8; 32]; 383 | self.sample_local_randomness(&mut rnd); 384 | let r = self.derive_random_values(&rnd); 385 | 386 | // key is then used for encrypting measurement and associated 387 | // data 388 | let key = self.derive_key(&r[0]); 389 | let share = self.share(&r[0], &r[1])?; 390 | let tag = r[2]; 391 | Ok(WASMSharingMaterial { key, share, tag }) 392 | } 393 | 394 | #[cfg(feature = "star2")] 395 | // Share with OPRF randomness (STAR) 396 | pub fn share_with_oprf_randomness( 397 | &self, 398 | oprf_server: &PPOPRFServer, 399 | ) -> WASMSharingMaterial { 400 | let mut rnd = vec![0u8; 32]; 401 | self.sample_oprf_randomness(oprf_server, &mut rnd); 402 | let r = self.derive_random_values(&rnd); 403 | 404 | // key is then used for encrypting measurement and associated 405 | // data 406 | let key = self.derive_key(&r[0]); 407 | let share = self.share(&r[0], &r[1]); 408 | let tag = r[2].clone(); 409 | WASMSharingMaterial { key, share, tag } 410 | } 411 | 412 | fn derive_random_values(&self, randomness: &[u8]) -> Vec<[u8; 32]> { 413 | let mut output = Vec::new(); 414 | for i in 0..3 { 415 | let mut to_fill = [0u8; 32]; 416 | strobe_digest( 417 | randomness, 418 | &[&[i as u8]], 419 | "star_derive_randoms", 420 | &mut to_fill, 421 | ); 422 | output.push(to_fill); 423 | } 424 | output 425 | } 426 | 427 | fn derive_key(&self, r1: &[u8]) -> [u8; 16] { 428 | let mut enc_key = [0u8; 16]; 429 | derive_ske_key(r1, &self.epoch, &mut enc_key); 430 | enc_key 431 | } 432 | 433 | fn share(&self, r1: &[u8], r2: &[u8]) -> Result> { 434 | let c = Commune::new(self.threshold, r1.to_vec(), r2.to_vec(), None); 435 | Ok(Share(c.share()?)) 436 | } 437 | 438 | pub fn sample_local_randomness(&self, out: &mut [u8]) { 439 | if out.len() != DIGEST_LEN { 440 | panic!( 441 | "Output buffer length ({}) does not match randomness length ({})", 442 | out.len(), 443 | DIGEST_LEN 444 | ); 445 | } 446 | strobe_digest( 447 | self.x.as_slice(), 448 | &[&self.epoch, &self.threshold.to_le_bytes()], 449 | "star_sample_local", 450 | out, 451 | ); 452 | } 453 | 454 | #[cfg(feature = "star2")] 455 | pub fn sample_oprf_randomness( 456 | &self, 457 | oprf_server: &PPOPRFServer, 458 | out: &mut [u8], 459 | ) { 460 | let mds = oprf_server.get_valid_metadata_tags(); 461 | let index = mds.iter().position(|r| r == &self.epoch).unwrap(); 462 | end_to_end_evaluation(oprf_server, self.x.as_slice(), index, true, out); 463 | } 464 | } 465 | 466 | // FIXME can we implement collect trait? 467 | pub fn share_recover(shares: &[Share]) -> Result> { 468 | recover( 469 | &shares 470 | .iter() 471 | .map(|share| share.0.clone()) 472 | .collect::>(), 473 | ) 474 | } 475 | 476 | // The `derive_ske_key` helper function derives symmetric encryption 477 | // keys that are used for encrypting/decrypting `Ciphertext` objects 478 | // during the STAR protocol. 479 | pub fn derive_ske_key(r1: &[u8], epoch: &[u8], key_out: &mut [u8]) { 480 | let mut to_fill = vec![0u8; 32]; 481 | strobe_digest(r1, &[epoch], "star_derive_ske_key", &mut to_fill); 482 | key_out.copy_from_slice(&to_fill[..16]); 483 | } 484 | 485 | pub fn strobe_digest(key: &[u8], ad: &[&[u8]], label: &str, out: &mut [u8]) { 486 | if out.len() != DIGEST_LEN { 487 | panic!( 488 | "Output buffer length ({}) does not match intended output length ({})", 489 | out.len(), 490 | DIGEST_LEN 491 | ); 492 | } else if ad.is_empty() { 493 | panic!("No additional data provided"); 494 | } 495 | let mut t = Strobe::new(label.as_bytes(), SecParam::B128); 496 | t.key(key, false); 497 | for x in ad.iter() { 498 | t.ad(x, false); 499 | } 500 | let mut rng: StrobeRng = t.into(); 501 | rng.fill(out); 502 | } 503 | -------------------------------------------------------------------------------- /star/src/strobe_rng.rs: -------------------------------------------------------------------------------- 1 | /// RngCore impl for the Strobe hash 2 | /// 3 | /// FIXME: This should be submitted upstream as a feature-gated 4 | /// extension. Failing that, publish as a separate helper crate 5 | /// to avoid this code duplication. 6 | use rand_core::{CryptoRng, RngCore}; 7 | use strobe_rs::Strobe; 8 | 9 | /// StrobeRng implements the RngCore trait by using STROBE as an entropy pool 10 | pub struct StrobeRng { 11 | strobe: Strobe, 12 | } 13 | 14 | impl From for StrobeRng { 15 | fn from(strobe: Strobe) -> Self { 16 | StrobeRng { strobe } 17 | } 18 | } 19 | 20 | impl RngCore for StrobeRng { 21 | fn next_u32(&mut self) -> u32 { 22 | rand_core::impls::next_u32_via_fill(self) 23 | } 24 | 25 | fn next_u64(&mut self) -> u64 { 26 | rand_core::impls::next_u64_via_fill(self) 27 | } 28 | 29 | fn fill_bytes(&mut self, dest: &mut [u8]) { 30 | let dest_len = (dest.len() as u32).to_le_bytes(); 31 | self.strobe.meta_ad(&dest_len, false); 32 | self.strobe.prf(dest, false); 33 | } 34 | 35 | fn try_fill_bytes( 36 | &mut self, 37 | dest: &mut [u8], 38 | ) -> Result<(), rand_core::Error> { 39 | self.fill_bytes(dest); 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl CryptoRng for StrobeRng {} 45 | -------------------------------------------------------------------------------- /star/test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "star-test-utils" 3 | version = "0.2.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | strobe-rs = "0.10.0" 10 | sta-rs = { path = "../" } 11 | rand = { version = "0.8.5", features = [ "std" ] } 12 | rayon = "1.7" 13 | zipf = "7.0.1" 14 | ppoprf = { path = "../../ppoprf" } 15 | -------------------------------------------------------------------------------- /star/test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | use std::fmt; 4 | 5 | use rand::distributions::Distribution; 6 | use rayon::prelude::*; 7 | 8 | use zipf::ZipfDistribution; 9 | 10 | use sta_rs::*; 11 | 12 | // The `zipf_measurement` function returns a client `Measurement` sampled from 13 | // Zipf power-law distribution with `n` corresponding to the number 14 | // of potential elements, and `s` the exponent. 15 | pub fn measurement_zipf(n: usize, s: f64) -> SingleMeasurement { 16 | let mut rng = rand::thread_rng(); 17 | let zipf = ZipfDistribution::new(n, s).unwrap(); 18 | let sample = zipf.sample(&mut rng).to_le_bytes(); 19 | let extended = sample.to_vec(); 20 | // essentially we compute a hash here so that we can simulate 21 | // having a full 32 bytes of data 22 | let mut to_fill = vec![0u8; 32]; 23 | strobe_digest(&[0u8; 32], &[&extended], "star_zipf_sample", &mut to_fill); 24 | SingleMeasurement::new(&to_fill) 25 | } 26 | 27 | pub fn client_zipf( 28 | n: usize, 29 | s: f64, 30 | threshold: u32, 31 | epoch: &[u8], 32 | ) -> MessageGenerator { 33 | let x = measurement_zipf(n, s); 34 | MessageGenerator::new(x, threshold, epoch) 35 | } 36 | 37 | // An `Output` corresponds to a single client `Measurement` sent to the 38 | // `AggregationServer` that satisfied the `threshold` check. Such 39 | // structs contain the `Measurement` value itself, along with a vector 40 | // of all the optional `AssociatedData` values sent by clients. 41 | pub struct Output { 42 | pub x: SingleMeasurement, 43 | pub aux: Vec>, 44 | } 45 | impl fmt::Debug for Output { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | f.debug_struct("Output") 48 | .field("tag", &self.x) 49 | .field("aux", &self.aux) 50 | .finish() 51 | } 52 | } 53 | 54 | #[derive(Debug)] 55 | enum AggServerError { 56 | PossibleShareCollision, 57 | } 58 | 59 | // The `AggregationServer` is the entity that processes `Client` 60 | // messages and learns `Measurement` values and `AssociatedData` if the 61 | // `threshold` is met. These servers possess no secret data. 62 | pub struct AggregationServer { 63 | pub threshold: u32, 64 | pub epoch: String, 65 | } 66 | impl AggregationServer { 67 | pub fn new(threshold: u32, epoch: &str) -> Self { 68 | AggregationServer { 69 | threshold, 70 | epoch: epoch.to_string(), 71 | } 72 | } 73 | 74 | pub fn retrieve_outputs(&self, all_messages: &[Message]) -> Vec { 75 | let filtered = self.filter_messages(all_messages); 76 | filtered 77 | .into_par_iter() 78 | .map(|messages| self.recover_measurements(&messages)) 79 | .map(|output| output.unwrap()) 80 | .collect() 81 | } 82 | 83 | fn recover_measurements( 84 | &self, 85 | messages: &[Message], 86 | ) -> Result { 87 | let mut enc_key_buf = vec![0u8; 16]; 88 | self.key_recover(messages, &mut enc_key_buf)?; 89 | 90 | let ciphertexts = messages.iter().map(|t| t.ciphertext.clone()); 91 | let plaintexts = 92 | ciphertexts.map(|c| c.decrypt(&enc_key_buf, "star_encrypt")); 93 | 94 | let splits: Vec<(Vec, Option)> = plaintexts 95 | .map(|p| { 96 | let mut slice = &p[..]; 97 | 98 | let measurement_bytes = load_bytes(slice).unwrap(); 99 | slice = &slice[4 + measurement_bytes.len()..]; 100 | if !slice.is_empty() { 101 | let aux_bytes = load_bytes(slice).unwrap(); 102 | if !aux_bytes.is_empty() { 103 | return ( 104 | measurement_bytes.to_vec(), 105 | Some(AssociatedData::new(aux_bytes)), 106 | ); 107 | } 108 | } 109 | (measurement_bytes.to_vec(), None) 110 | }) 111 | .collect(); 112 | let tag = &splits[0].0; 113 | for new_tag in splits.iter().skip(1) { 114 | if &new_tag.0 != tag { 115 | panic!("tag mismatch ({:?} != {:?})", tag, new_tag.0); 116 | } 117 | } 118 | Ok(Output { 119 | x: SingleMeasurement::new(tag), 120 | aux: splits.into_iter().map(|val| val.1).collect(), 121 | }) 122 | } 123 | 124 | fn key_recover( 125 | &self, 126 | messages: &[Message], 127 | enc_key: &mut [u8], 128 | ) -> Result<(), AggServerError> { 129 | let shares: Vec = 130 | messages.iter().map(|triple| triple.share.clone()).collect(); 131 | let res = share_recover(&shares); 132 | if res.is_err() { 133 | return Err(AggServerError::PossibleShareCollision); 134 | } 135 | let message = res.unwrap().get_message(); 136 | derive_ske_key(&message, self.epoch.as_bytes(), enc_key); 137 | Ok(()) 138 | } 139 | 140 | fn filter_messages(&self, messages: &[Message]) -> Vec> { 141 | let collected = self.collect_messages(messages); 142 | collected 143 | .into_iter() 144 | .filter(|bucket| bucket.len() >= (self.threshold as usize)) 145 | .collect() 146 | } 147 | 148 | fn collect_messages(&self, messages: &[Message]) -> Vec> { 149 | let mut collected_messages: HashMap> = HashMap::new(); 150 | for triple in messages { 151 | let s = format!("{:x?}", triple.tag); 152 | match collected_messages.entry(s) { 153 | Entry::Vacant(e) => { 154 | e.insert(vec![triple.clone()]); 155 | } 156 | Entry::Occupied(mut e) => { 157 | e.get_mut().push(triple.clone()); 158 | } 159 | } 160 | } 161 | collected_messages.values().cloned().collect() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /star/tests/e2e.rs: -------------------------------------------------------------------------------- 1 | //! End-to-end tests verifying the protocol implementation 2 | 3 | use sta_rs::*; 4 | use star_test_utils::{client_zipf, AggregationServer}; 5 | 6 | #[cfg(feature = "star2")] 7 | use ppoprf::ppoprf::{end_to_end_evaluation, Server as PPOPRFServer}; 8 | 9 | // TODO implement star2 fully 10 | #[cfg(not(feature = "star2"))] 11 | pub struct PPOPRFServer; 12 | 13 | #[test] 14 | fn serialize_ciphertext() { 15 | let mg = MessageGenerator::new( 16 | SingleMeasurement::new(b"foobar"), 17 | 0, 18 | "epoch".as_bytes(), 19 | ); 20 | let mut rnd = [0u8; 32]; 21 | mg.sample_local_randomness(&mut rnd); 22 | let triple = Message::generate(&mg, &rnd, None) 23 | .expect("Failed to generate message triplet"); 24 | let bytes = triple.ciphertext.to_bytes(); 25 | assert_eq!(Ciphertext::from_bytes(&bytes), triple.ciphertext); 26 | } 27 | 28 | #[test] 29 | fn serialize_triple() { 30 | let mg = MessageGenerator::new( 31 | SingleMeasurement::new(b"foobar"), 32 | 0, 33 | "epoch".as_bytes(), 34 | ); 35 | let mut rnd = [0u8; 32]; 36 | mg.sample_local_randomness(&mut rnd); 37 | let triple = Message::generate(&mg, &rnd, None) 38 | .expect("Failed to generate message triplet"); 39 | let bytes = triple.to_bytes(); 40 | assert_eq!(Message::from_bytes(&bytes), Some(triple)); 41 | } 42 | 43 | #[test] 44 | fn roundtrip() { 45 | let mg = MessageGenerator::new( 46 | SingleMeasurement::new(b"foobar"), 47 | 1, 48 | "epoch".as_bytes(), 49 | ); 50 | let mut rnd = [0u8; 32]; 51 | mg.sample_local_randomness(&mut rnd); 52 | let triple = Message::generate(&mg, &rnd, None) 53 | .expect("Failed to generate message triplet"); 54 | 55 | let commune = share_recover(&[triple.share]).unwrap(); 56 | let message = commune.get_message(); 57 | 58 | let mut enc_key_buf = vec![0u8; 16]; 59 | derive_ske_key(&message, "epoch".as_bytes(), &mut enc_key_buf); 60 | let plaintext = triple.ciphertext.decrypt(&enc_key_buf, "star_encrypt"); 61 | let mut slice = &plaintext[..]; 62 | 63 | let measurement_bytes = load_bytes(slice).unwrap(); 64 | slice = &slice[4 + measurement_bytes.len()..]; 65 | 66 | if !slice.is_empty() { 67 | let aux_bytes = load_bytes(slice).unwrap(); 68 | assert_eq!(aux_bytes.len(), 0); 69 | } 70 | 71 | assert_eq!(measurement_bytes, b"foobar"); 72 | } 73 | 74 | #[test] 75 | fn star1_no_aux_multiple_block() { 76 | star_no_aux_multiple_block(None); 77 | } 78 | 79 | #[test] 80 | fn star1_no_aux_single_block() { 81 | star_no_aux_single_block(None); 82 | } 83 | 84 | #[test] 85 | fn star1_with_aux_multiple_block() { 86 | star_with_aux_multiple_block(None); 87 | } 88 | 89 | #[test] 90 | fn star1_rand_with_aux_multiple_block() { 91 | star_rand_with_aux_multiple_block(None); 92 | } 93 | 94 | #[cfg(feature = "star2")] 95 | #[test] 96 | fn star2_no_aux_multiple_block() { 97 | let mds: &[Vec] = &[b"t".to_vec()]; 98 | star_no_aux_multiple_block(Some(PPOPRFServer::new(&mds))); 99 | } 100 | 101 | #[cfg(feature = "star2")] 102 | #[test] 103 | fn star2_no_aux_single_block() { 104 | let mds: &[Vec] = &[b"t".to_vec()]; 105 | star_no_aux_single_block(Some(PPOPRFServer::new(&mds))); 106 | } 107 | 108 | #[cfg(feature = "star2")] 109 | #[test] 110 | fn star2_with_aux_multiple_block() { 111 | let mds: &[Vec] = &[b"t".to_vec()]; 112 | star_with_aux_multiple_block(Some(PPOPRFServer::new(&mds))); 113 | } 114 | 115 | #[cfg(feature = "star2")] 116 | #[test] 117 | fn star2_rand_with_aux_multiple_block() { 118 | let mds: &[Vec] = &[b"t".to_vec()]; 119 | star_rand_with_aux_multiple_block(Some(PPOPRFServer::new(&mds))); 120 | } 121 | 122 | fn star_no_aux_multiple_block(oprf_server: Option) { 123 | let mut clients = Vec::new(); 124 | let threshold = 2; 125 | let epoch = "t"; 126 | let str1 = "hello world"; 127 | let str2 = "goodbye sweet prince"; 128 | for i in 0..10 { 129 | if i % 3 == 0 { 130 | clients.push(MessageGenerator::new( 131 | SingleMeasurement::new(str1.as_bytes()), 132 | threshold, 133 | epoch.as_bytes(), 134 | )); 135 | } else if i % 4 == 0 { 136 | clients.push(MessageGenerator::new( 137 | SingleMeasurement::new(str2.as_bytes()), 138 | threshold, 139 | epoch.as_bytes(), 140 | )); 141 | } else { 142 | clients.push(MessageGenerator::new( 143 | SingleMeasurement::new(&[i as u8]), 144 | threshold, 145 | epoch.as_bytes(), 146 | )); 147 | } 148 | } 149 | let agg_server = AggregationServer::new(threshold, epoch); 150 | 151 | let messages: Vec = clients 152 | .into_iter() 153 | .map(|mg| { 154 | let mut rnd = [0u8; 32]; 155 | if oprf_server.is_none() { 156 | mg.sample_local_randomness(&mut rnd); 157 | } else { 158 | #[cfg(feature = "star2")] 159 | mg.sample_oprf_randomness(oprf_server, &mut rnd); 160 | } 161 | Message::generate(&mg, &rnd, None).unwrap() 162 | }) 163 | .collect(); 164 | let outputs = agg_server.retrieve_outputs(&messages[..]); 165 | for o in outputs { 166 | let tag_str = std::str::from_utf8(o.x.as_slice()) 167 | .unwrap() 168 | .trim_end_matches(char::from(0)); 169 | if tag_str == str1 { 170 | assert_eq!(o.aux.len(), 4); 171 | } else if tag_str == str2 { 172 | assert_eq!(o.aux.len(), 2); 173 | } else { 174 | panic!("Unexpected tag: {}", tag_str); 175 | } 176 | 177 | if let Some(b) = o.aux.into_iter().flatten().next() { 178 | panic!("Unexpected auxiliary data: {:?}", b); 179 | } 180 | } 181 | } 182 | 183 | fn star_no_aux_single_block(oprf_server: Option) { 184 | let mut clients = Vec::new(); 185 | let threshold = 2; 186 | let epoch = "t"; 187 | let str1 = "three"; 188 | let str2 = "four"; 189 | for i in 0..10 { 190 | if i % 3 == 0 { 191 | clients.push(MessageGenerator::new( 192 | SingleMeasurement::new(str1.as_bytes()), 193 | threshold, 194 | epoch.as_bytes(), 195 | )); 196 | } else if i % 4 == 0 { 197 | clients.push(MessageGenerator::new( 198 | SingleMeasurement::new(str2.as_bytes()), 199 | threshold, 200 | epoch.as_bytes(), 201 | )); 202 | } else { 203 | clients.push(MessageGenerator::new( 204 | SingleMeasurement::new(&[i as u8]), 205 | threshold, 206 | epoch.as_bytes(), 207 | )); 208 | } 209 | } 210 | let agg_server = AggregationServer::new(threshold, epoch); 211 | 212 | let messages: Vec = clients 213 | .into_iter() 214 | .map(|mg| { 215 | let mut rnd = [0u8; 32]; 216 | if oprf_server.is_none() { 217 | mg.sample_local_randomness(&mut rnd); 218 | } else { 219 | #[cfg(feature = "star2")] 220 | mg.sample_oprf_randomness(oprf_server, &mut rnd); 221 | } 222 | Message::generate(&mg, &rnd, None) 223 | .expect("Failed to generate message triplet") 224 | }) 225 | .collect(); 226 | let outputs = agg_server.retrieve_outputs(&messages); 227 | for o in outputs { 228 | let tag_str = std::str::from_utf8(o.x.as_slice()) 229 | .unwrap() 230 | .trim_end_matches(char::from(0)); 231 | if tag_str == str1 { 232 | assert_eq!(o.aux.len(), 4); 233 | } else if tag_str == str2 { 234 | assert_eq!(o.aux.len(), 2); 235 | } else { 236 | panic!("Unexpected tag: {}", tag_str); 237 | } 238 | 239 | if let Some(b) = o.aux.into_iter().flatten().next() { 240 | panic!("Unexpected auxiliary data: {:?}", b); 241 | } 242 | } 243 | } 244 | 245 | fn star_with_aux_multiple_block(oprf_server: Option) { 246 | // Generate an assortment of client messages. 247 | let mut clients = Vec::new(); 248 | let threshold = 2; 249 | let epoch = "t"; 250 | let str1 = "hello world"; 251 | let str2 = "goodbye sweet prince"; 252 | let message_count = 10; 253 | for i in 0..message_count { 254 | if i % 3 == 0 { 255 | // Periodically generate the same message. 256 | clients.push(MessageGenerator::new( 257 | SingleMeasurement::new(str1.as_bytes()), 258 | threshold, 259 | epoch.as_bytes(), 260 | )); 261 | } else if i % 4 == 0 { 262 | // Another periodically-generated message. 263 | clients.push(MessageGenerator::new( 264 | SingleMeasurement::new(str2.as_bytes()), 265 | threshold, 266 | epoch.as_bytes(), 267 | )); 268 | } else { 269 | // Unique measurements which will not meet threshold. 270 | clients.push(MessageGenerator::new( 271 | SingleMeasurement::new(&[i]), 272 | threshold, 273 | epoch.as_bytes(), 274 | )); 275 | } 276 | } 277 | 278 | // Append some associated data and generate submissions. 279 | let messages: Vec = clients 280 | .into_iter() 281 | .zip(0..) 282 | .map(|(mg, counter)| { 283 | let mut rnd = [0u8; 32]; 284 | if oprf_server.is_none() { 285 | mg.sample_local_randomness(&mut rnd); 286 | } else { 287 | #[cfg(feature = "star2")] 288 | mg.sample_oprf_randomness(oprf_server, &mut rnd) 289 | } 290 | Message::generate(&mg, &rnd, Some(AssociatedData::new(&[counter; 1]))) 291 | .unwrap() 292 | }) 293 | .collect(); 294 | 295 | // Aggregate and recover messages meeting threshold. 296 | let agg_server = AggregationServer::new(threshold, epoch); 297 | let outputs = agg_server.retrieve_outputs(&messages); 298 | for o in outputs { 299 | // Confirm the expected messages met threshold and no others. 300 | let tag_str = std::str::from_utf8(o.x.as_slice()) 301 | .unwrap() 302 | .trim_end_matches(char::from(0)); 303 | if tag_str == str1 { 304 | assert_eq!(o.aux.len(), 4); 305 | } else if tag_str == str2 { 306 | assert_eq!(o.aux.len(), 2); 307 | } else { 308 | panic!("Unexpected tag: {}", tag_str); 309 | } 310 | 311 | // Confirm the expected AssociatedData values are recovered. 312 | assert!( 313 | o.aux.iter().all(|v| v.is_some()), 314 | "Expected auxiliary data from all submissions!" 315 | ); 316 | for b in o.aux.iter().flatten() { 317 | let v = b.as_slice(); 318 | assert_eq!(v.len(), 1, "Expected auxiliary data to be a single byte!"); 319 | assert!( 320 | v[0] < message_count, 321 | "Auxiliary data should be the in range of the message count!" 322 | ); 323 | if tag_str == str1 { 324 | assert!(v[0] % 3 == 0); 325 | } else if tag_str == str2 { 326 | assert!(v[0] % 4 == 0); 327 | } else { 328 | panic!("Auxiliary data has unexpected value: {}", v[0]); 329 | } 330 | } 331 | } 332 | } 333 | 334 | fn star_rand_with_aux_multiple_block(oprf_server: Option) { 335 | let mut clients = Vec::new(); 336 | let threshold = 5; 337 | let epoch = "t"; 338 | for _ in 0..254 { 339 | clients.push(client_zipf(1000, 1.03, threshold, epoch.as_bytes())); 340 | } 341 | let agg_server = AggregationServer::new(threshold, epoch); 342 | 343 | let messages: Vec = clients 344 | .into_iter() 345 | .zip(0..) 346 | .map(|(mg, counter)| { 347 | let mut rnd = [0u8; 32]; 348 | if oprf_server.is_none() { 349 | mg.sample_local_randomness(&mut rnd); 350 | } else { 351 | #[cfg(feature = "star2")] 352 | mg.sample_oprf_randomness(oprf_server, &mut rnd); 353 | } 354 | Message::generate(&mg, &rnd, Some(AssociatedData::new(&[counter; 4]))) 355 | .unwrap() 356 | }) 357 | .collect(); 358 | let outputs = agg_server.retrieve_outputs(&messages[..]); 359 | for o in outputs { 360 | for aux in o.aux { 361 | if aux.is_none() { 362 | panic!("Expected auxiliary data"); 363 | } else if let Some(a) = aux { 364 | let val = a.as_slice()[0]; 365 | assert!(val < 255); 366 | for i in 1..3 { 367 | assert_eq!(a.as_slice()[i], val); 368 | } 369 | } 370 | } 371 | } 372 | } 373 | --------------------------------------------------------------------------------