├── .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 | [](https://github.com/brave/sta-rs/actions)
4 | [](https://crates.io/crates/star-sharks)
5 | [](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