├── taplo.toml ├── .github ├── dependabot.yml └── workflows │ ├── publish.yml │ └── main.yml ├── rustfmt.toml ├── .gitignore ├── .cargo └── license.rs ├── src ├── tests │ ├── mod.rs │ ├── mock_rng.rs │ ├── parser.rs │ └── test_cfrg_vectors.rs ├── error.rs ├── ciphersuite.rs ├── group │ ├── tests.rs │ ├── elliptic_curve.rs │ ├── ristretto.rs │ └── mod.rs ├── serialization.rs ├── oprf.rs ├── common.rs ├── lib.rs ├── voprf.rs └── poprf.rs ├── LICENSE-MIT ├── README.md ├── CONTRIBUTING.md ├── CHANGELOG.md ├── Cargo.toml ├── CODE_OF_CONDUCT.md └── LICENSE-APACHE /taplo.toml: -------------------------------------------------------------------------------- 1 | [formatting] 2 | allowed_blank_lines = 1 3 | reorder_keys = true 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: cargo 5 | directory: / 6 | schedule: 7 | interval: daily 8 | 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | format_strings = true 3 | group_imports = "StdExternalCrate" 4 | imports_granularity = "Module" 5 | license_template_path = ".cargo/license.rs" 6 | newline_style = "Unix" 7 | unstable_features = true 8 | wrap_comments = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /.cargo/license.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | mod cfrg_vectors; 10 | mod mock_rng; 11 | mod parser; 12 | mod test_cfrg_vectors; 13 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | rust: [stable] 14 | 15 | steps: 16 | - uses: hecrj/setup-rust-action@v2 17 | with: 18 | rust-version: ${{ matrix.rust }} 19 | - uses: actions/checkout@master 20 | - name: Login to crates.io 21 | run: cargo login $CRATES_IO_TOKEN 22 | env: 23 | CRATES_IO_TOKEN: ${{ secrets.crates_io_token }} 24 | - name: Dry run publish voprf 25 | run: cargo publish --dry-run --manifest-path Cargo.toml 26 | - name: Publish voprf 27 | run: cargo publish --manifest-path Cargo.toml 28 | env: 29 | CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }} 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voprf ![Build Status](https://github.com/novifinancial/voprf/workflows/Rust%20CI/badge.svg) 2 | An implementation of a (verifiable) oblivious pseudorandom function (VOPRF) 3 | 4 | A VOPRF is a verifiable oblivious pseudorandom function, a protocol between a client and a server. The regular (non-verifiable) OPRF is also supported in this implementation. 5 | 6 | This implementation is based on [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497). 7 | 8 | Documentation 9 | ------------- 10 | 11 | The API can be found [here](https://docs.rs/voprf/) along with an example for usage. 12 | 13 | Installation 14 | ------------ 15 | 16 | Add the following line to the dependencies of your `Cargo.toml`: 17 | 18 | ``` 19 | voprf = "0.6.0-pre.0" 20 | ``` 21 | 22 | ### Minimum Supported Rust Version 23 | 24 | Rust **1.65** or higher. 25 | 26 | Contributors 27 | ------------ 28 | 29 | The author of this code is Kevin Lewi ([@kevinlewi](https://github.com/kevinlewi)). 30 | To learn more about contributing to this project, [see this document](./CONTRIBUTING.md). 31 | 32 | License 33 | ------- 34 | 35 | This project is dual-licensed under either the [MIT license](./LICENSE-MIT) 36 | or the [Apache License, Version 2.0](./LICENSE-APACHE). 37 | You may select, at your option, one of the above-listed licenses. 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this library 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Pull Requests 6 | We actively welcome your pull requests. 7 | 8 | 1. Fork the repo and create your branch from `main`. 9 | 2. If you've added code that should be tested, add tests. 10 | 3. If you've changed APIs, update the documentation. 11 | 4. Ensure the test suite passes. 12 | 5. If you haven't already, complete the Contributor License Agreement ("CLA"). 13 | 14 | ## Contributor License Agreement ("CLA") 15 | In order to accept your pull request, we need you to submit a CLA. You only need 16 | to do this once to work on any of Facebook's open source projects. 17 | 18 | Complete your CLA here: 19 | 20 | ## Issues 21 | We use GitHub issues to track public bugs. Please ensure your description is 22 | clear and has sufficient instructions to be able to reproduce the issue. 23 | 24 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 25 | disclosure of security bugs. In those cases, please go through the process 26 | outlined on that page and do not file a public issue. 27 | 28 | ## License 29 | By contributing to voprf, you agree that your contributions will be 30 | licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root 31 | directory of this source tree. 32 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Errors which are produced during an execution of the protocol 10 | 11 | /// [`Result`](core::result::Result) shorthand that uses [`Error`]. 12 | pub type Result = core::result::Result; 13 | 14 | /// Represents an error in the manipulation of internal cryptographic data 15 | #[derive(Clone, Copy, Debug, displaydoc::Display, Eq, Hash, Ord, PartialEq, PartialOrd)] 16 | pub enum Error { 17 | /// Size of info is longer then [`u16::MAX`]. 18 | Info, 19 | /// Size of input is empty or longer then [`u16::MAX`]. 20 | Input, 21 | /// Size of info and seed together are longer then `u16::MAX - 3`. 22 | DeriveKeyPair, 23 | /// Failure to deserialize bytes 24 | Deserialization, 25 | /// Batched items are more then [`u16::MAX`] or length don't match. 26 | Batch, 27 | /// In verifiable mode, occurs when the proof failed to verify 28 | ProofVerification, 29 | /// The protocol has failed and can't be completed. 30 | Protocol, 31 | } 32 | 33 | /// Only used to implement [`Group`](crate::Group). 34 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 35 | pub enum InternalError { 36 | /// Size of input is empty or longer then [`u16::MAX`]. 37 | Input, 38 | /// `input` is longer then [`u16::MAX`]. 39 | I2osp, 40 | } 41 | 42 | impl core::error::Error for Error {} 43 | -------------------------------------------------------------------------------- /src/ciphersuite.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Defines the CipherSuite trait to specify the underlying primitives for VOPRF 10 | 11 | use digest::core_api::BlockSizeUser; 12 | use digest::{FixedOutput, HashMarker, OutputSizeUser}; 13 | use elliptic_curve::VoprfParameters; 14 | use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; 15 | use generic_array::ArrayLength; 16 | 17 | use crate::Group; 18 | 19 | /// Configures the underlying primitives used in VOPRF 20 | pub trait CipherSuite 21 | where 22 | ::OutputSize: 23 | ArrayLength + IsLess + IsLessOrEqual<::BlockSize>, 24 | { 25 | /// The ciphersuite identifier as dictated by 26 | /// 27 | const ID: &'static str; 28 | 29 | /// A finite cyclic group along with a point representation that allows some 30 | /// customization on how to hash an input to a curve point. See [`Group`]. 31 | type Group: Group; 32 | 33 | /// The main hash function to use (for HKDF computations and hashing 34 | /// transcripts). 35 | type Hash: BlockSizeUser + Default + FixedOutput + HashMarker; 36 | } 37 | 38 | impl CipherSuite for T 39 | where 40 | T: Group, 41 | T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker, 42 | ::OutputSize: 43 | ArrayLength + IsLess + IsLessOrEqual<::BlockSize>, 44 | { 45 | const ID: &'static str = T::ID; 46 | 47 | type Group = T; 48 | 49 | type Hash = T::Hash; 50 | } 51 | -------------------------------------------------------------------------------- /src/tests/mock_rng.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | use alloc::vec::Vec; 10 | use core::cmp::min; 11 | 12 | use rand_core::{CryptoRng, Error, RngCore}; 13 | 14 | /// A simple implementation of `RngCore` for testing purposes. 15 | /// 16 | /// This generates a cyclic sequence (i.e. cycles over an initial buffer) 17 | #[derive(Debug, Clone)] 18 | pub struct CycleRng { 19 | v: Vec, 20 | } 21 | 22 | impl CycleRng { 23 | /// Create a `CycleRng`, yielding a sequence starting with `initial` and 24 | /// looping thereafter 25 | pub fn new(initial: Vec) -> Self { 26 | CycleRng { v: initial } 27 | } 28 | } 29 | 30 | fn rotate_left(data: &mut [T], steps: usize) { 31 | if data.is_empty() { 32 | return; 33 | } 34 | let steps = steps % data.len(); 35 | 36 | data[..steps].reverse(); 37 | data[steps..].reverse(); 38 | data.reverse(); 39 | } 40 | 41 | impl RngCore for CycleRng { 42 | fn next_u32(&mut self) -> u32 { 43 | unimplemented!() 44 | } 45 | 46 | #[inline] 47 | fn next_u64(&mut self) -> u64 { 48 | unimplemented!() 49 | } 50 | 51 | #[inline] 52 | fn fill_bytes(&mut self, dest: &mut [u8]) { 53 | let len = min(self.v.len(), dest.len()); 54 | dest[..len].copy_from_slice(&self.v[..len]); 55 | rotate_left(&mut self.v, len); 56 | } 57 | 58 | #[inline] 59 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { 60 | self.fill_bytes(dest); 61 | Ok(()) 62 | } 63 | } 64 | 65 | // This is meant for testing only 66 | impl CryptoRng for CycleRng {} 67 | -------------------------------------------------------------------------------- /src/group/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Includes a series of tests for the group implementations 10 | 11 | use crate::{Error, Group, Result}; 12 | 13 | // Test that the deserialization of a group element should throw an error if the 14 | // identity element can be deserialized properly 15 | 16 | #[test] 17 | fn test_group_properties() -> Result<()> { 18 | use p256::NistP256; 19 | use p384::NistP384; 20 | use p521::NistP521; 21 | 22 | #[cfg(feature = "ristretto255")] 23 | { 24 | use crate::Ristretto255; 25 | 26 | test_identity_element_error::()?; 27 | test_zero_scalar_error::()?; 28 | } 29 | 30 | test_identity_element_error::()?; 31 | test_zero_scalar_error::()?; 32 | 33 | test_identity_element_error::()?; 34 | test_zero_scalar_error::()?; 35 | 36 | test_identity_element_error::()?; 37 | test_zero_scalar_error::()?; 38 | 39 | Ok(()) 40 | } 41 | 42 | // Checks that the identity element cannot be deserialized 43 | fn test_identity_element_error() -> Result<()> { 44 | let identity = G::identity_elem(); 45 | let result = G::deserialize_elem(&G::serialize_elem(identity)); 46 | assert!(matches!(result, Err(Error::Deserialization))); 47 | 48 | Ok(()) 49 | } 50 | 51 | // Checks that the zero scalar cannot be deserialized 52 | fn test_zero_scalar_error() -> Result<()> { 53 | let zero_scalar = G::zero_scalar(); 54 | let result = G::deserialize_scalar(&G::serialize_scalar(zero_scalar)); 55 | assert!(matches!(result, Err(Error::Deserialization))); 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.6.0-pre.0 (November 8, 2025) 4 | * MSRV bumped to 1.83 5 | * Updated Ristretto255 random scalar generation 6 | * Updated generic-array to v1 7 | 8 | ## 0.5.0 (March 6, 2024) 9 | * Just a version bump from v0.5.0-pre.7 10 | 11 | ## 0.5.0-pre.7 (January 11, 2024) 12 | * Updated to be in sync with RFC 9497 13 | 14 | ## 0.5.0-pre.6 (July 24, 2023) 15 | * Updated curve25519-dalek dependency to 4 16 | 17 | ## 0.5.0-pre.5 (June 27, 2023) 18 | * Updated curve25519-dalek dependency to 4.0.0-rc.3 19 | 20 | ## 0.5.0-pre.4 (May 20, 2023) 21 | * Updated curve25519-dalek dependency to 4.0.0-rc.2 22 | 23 | ## 0.5.0-pre.3 (March 4, 2023) 24 | * Updated to be in sync with draft-irtf-cfrg-voprf-19 25 | * Increased MSRV to 1.65 26 | * Updated p256 dependency to v0.13 27 | * Added p384 tests 28 | 29 | ## 0.5.0-pre.2 (February 3, 2023) 30 | * Increased MSRV to 1.60 31 | * Updated p256 dependency to v0.12 32 | * Updated curve25519-dalek dependency to 4.0.0-rc.1 33 | 34 | ## 0.5.0-pre.1 (December 19, 2022) 35 | * Updated curve25519-dalek dependency to 4.0.0-pre.5 36 | 37 | ## 0.4.0 (September 15, 2022) 38 | * Updated to be in sync with draft-irtf-cfrg-voprf-11, with 39 | the addition of the POPRF mode 40 | * Adds the evaluate() function to the servers to calculate the output of the OPRF 41 | directly 42 | * Renames the former evaluate() function to blind_evaluate to match the spec 43 | * Fixes the order of parameters for PoprfClient::blind to align it with the 44 | other clients 45 | * Exposes the derive_key function under the "danger" feature 46 | * Added support for running the API without performing allocations 47 | * Revamped the way the Group trait was used, so as to be more easily 48 | extendable to other groups 49 | * Added common traits for each public-facing struct, including serde 50 | support 51 | 52 | ## 0.3.0 (October 25, 2021) 53 | 54 | * Updated to be in sync with draft-irtf-cfrg-voprf-08 55 | 56 | ## 0.2.0 (October 18, 2021) 57 | 58 | * Removed the CipherSuite interface 59 | * Added the "danger" feature for exposing internal functions 60 | * General improvements to the group interface 61 | 62 | ## 0.1.0 (September 29, 2021) 63 | 64 | * Initial release 65 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Kevin Lewi "] 3 | categories = ["no-std", "algorithms", "cryptography"] 4 | description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)" 5 | edition = "2021" 6 | keywords = ["oprf"] 7 | license = "MIT" 8 | name = "voprf" 9 | readme = "README.md" 10 | repository = "https://github.com/facebook/voprf/" 11 | rust-version = "1.83" 12 | version = "0.6.0-pre.0" 13 | 14 | [features] 15 | alloc = [] 16 | danger = [] 17 | default = ["ristretto255-ciphersuite", "dep:serde"] 18 | ristretto255 = ["dep:curve25519-dalek"] 19 | ristretto255-ciphersuite = ["ristretto255", "dep:sha2"] 20 | serde = ["curve25519-dalek?/serde", "generic-array/serde", "dep:serde"] 21 | std = ["alloc"] 22 | 23 | [dependencies] 24 | curve25519-dalek = { version = "4", default-features = false, features = [ 25 | "rand_core", 26 | "zeroize", 27 | ], optional = true } 28 | derive-where = { version = "1", features = ["zeroize-on-drop"] } 29 | digest = "0.10" 30 | displaydoc = { version = "0.2", default-features = false } 31 | elliptic-curve = { version = "0.13", features = [ 32 | "hash2curve", 33 | "sec1", 34 | "voprf", 35 | ] } 36 | generic-array = "1" 37 | rand_core = { version = "0.6", default-features = false } 38 | serde = { version = "1", default-features = false, features = [ 39 | "derive", 40 | ], optional = true } 41 | sha2 = { version = "0.10", default-features = false, optional = true } 42 | subtle = { version = "2.3", default-features = false } 43 | zeroize = { version = "1.5", default-features = false } 44 | 45 | [dev-dependencies] 46 | generic-array = { version = "1" } 47 | hex = "0.4" 48 | p256 = { version = "0.13", default-features = false, features = [ 49 | "hash2curve", 50 | "voprf", 51 | ] } 52 | p384 = { version = "0.13", default-features = false, features = [ 53 | "hash2curve", 54 | "voprf", 55 | ] } 56 | p521 = { version = "0.13.3", default-features = false, features = [ 57 | "hash2curve", 58 | "voprf", 59 | ] } 60 | proptest = "1" 61 | rand = "0.8" 62 | regex = "1" 63 | serde_json = "1" 64 | sha2 = "0.10" 65 | 66 | [package.metadata.docs.rs] 67 | all-features = true 68 | rustdoc-args = ["--cfg", "docsrs"] 69 | targets = [] 70 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/tests/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | use alloc::string::{String, ToString}; 10 | use alloc::vec::Vec; 11 | use alloc::{format, vec}; 12 | 13 | pub(crate) fn rfc_to_json(input: &str) -> String { 14 | format!("{{\n{}\n}}", parse_ciphersuites(input)) 15 | } 16 | 17 | fn parse_ciphersuites(input: &str) -> String { 18 | let re = regex::Regex::new(r"\nA\.\d\. (?P.+?)\n\n").unwrap(); 19 | let mut ciphersuites = vec![]; 20 | 21 | let chunks: Vec<&str> = re.split(input).collect(); 22 | let mut count = 1; 23 | for caps in re.captures_iter(input) { 24 | let ciphersuite = format!( 25 | "\"{}\": {{ {} }}", 26 | &caps["ciphersuite"], 27 | parse_modes(chunks[count]) 28 | ); 29 | ciphersuites.push(ciphersuite); 30 | count += 1; 31 | } 32 | 33 | ciphersuites.join(",\n") 34 | } 35 | 36 | fn parse_modes(input: &str) -> String { 37 | let re = regex::Regex::new(r"A\.\d.\d\. (?P.*?) Mode").unwrap(); 38 | let mut modes = vec![]; 39 | 40 | let chunks: Vec<&str> = re.split(input).collect(); 41 | let mut count = 1; 42 | for caps in re.captures_iter(input) { 43 | let mode = format!( 44 | "\"{}\": [\n {} \n]", 45 | &caps["mode"], 46 | parse_vectors(chunks[count]) 47 | ); 48 | modes.push(mode); 49 | count += 1; 50 | } 51 | 52 | modes.join(",\n") 53 | } 54 | 55 | fn parse_vectors(input: &str) -> String { 56 | let re = regex::Regex::new(r"A\.\d.\d\.\d\. Test Vector.*+\n").unwrap(); 57 | let mut vectors = vec![]; 58 | 59 | let chunks: Vec<&str> = re.split(input).collect(); 60 | let init_params = parse_params(chunks[0]); 61 | 62 | let mut count = 1; 63 | for _ in re.captures_iter(input) { 64 | let params = format!("{{\n{},\n{}\n}}", init_params, parse_params(chunks[count])); 65 | vectors.push(params); 66 | count += 1; 67 | } 68 | 69 | vectors.join(",\n") 70 | } 71 | 72 | fn parse_params(input: &str) -> String { 73 | let mut params = vec![]; 74 | let mut param = String::new(); 75 | 76 | let mut lines = input.lines(); 77 | 78 | loop { 79 | match lines.next() { 80 | None => { 81 | // Clear out any existing string and flush to params 82 | param += "\""; 83 | params.push(param); 84 | 85 | return params.join(",\n"); 86 | } 87 | Some(line) => { 88 | // If line contains =, then 89 | if line.contains('=') { 90 | // Clear out any existing string and flush to params 91 | if !param.is_empty() { 92 | param += "\""; 93 | params.push(param); 94 | } 95 | 96 | let mut iter = line.split('='); 97 | let key = iter.next().unwrap().split_whitespace().next().unwrap(); 98 | let val = iter.next().unwrap().split_whitespace().next().unwrap(); 99 | 100 | param = format!(" \"{key}\": \"{val}"); 101 | } else { 102 | let s = line.trim().to_string(); 103 | if s.contains('~') || s.contains('#') { 104 | // Ignore comment lines 105 | continue; 106 | } 107 | if !s.is_empty() { 108 | param += &s; 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/group/elliptic_curve.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | use core::ops::Add; 10 | 11 | use digest::core_api::BlockSizeUser; 12 | use digest::{FixedOutput, HashMarker}; 13 | use elliptic_curve::group::cofactor::CofactorGroup; 14 | use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest}; 15 | use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}; 16 | use elliptic_curve::{ 17 | AffinePoint, Field, FieldBytes, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar, 18 | SecretKey, 19 | }; 20 | use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; 21 | use generic_array::{ArrayLength, GenericArray}; 22 | use rand_core::{CryptoRng, RngCore}; 23 | 24 | use super::Group; 25 | use crate::{Error, InternalError, Result}; 26 | 27 | type ElemLen = as ModulusSize>::CompressedPointSize; 28 | type ScalarLen = FieldBytesSize; 29 | 30 | impl Group for C 31 | where 32 | C: GroupDigest, 33 | ProjectivePoint: CofactorGroup + ToEncodedPoint, 34 | ScalarLen: ModulusSize, 35 | ScalarLen: ArrayLength, 36 | AffinePoint: FromEncodedPoint + ToEncodedPoint, 37 | Scalar: FromOkm, 38 | // `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen` 39 | ScalarLen: Add>, 40 | Sum, ElemLen>: ArrayLength, 41 | // `ProofLen` 42 | ScalarLen: Add>, 43 | Sum, ScalarLen>: ArrayLength, 44 | ElemLen: ArrayLength, 45 | { 46 | type Elem = ProjectivePoint; 47 | 48 | type ElemLen = ElemLen; 49 | 50 | type Scalar = Scalar; 51 | 52 | type ScalarLen = ScalarLen; 53 | 54 | // Implements the `hash_to_curve()` function from 55 | // https://www.rfc-editor.org/rfc/rfc9380.html#section-3 56 | fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result 57 | where 58 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 59 | H::OutputSize: IsLess + IsLessOrEqual, 60 | { 61 | Self::hash_from_bytes::>(input, dst).map_err(|_| InternalError::Input) 62 | } 63 | 64 | // Implements the `HashToScalar()` function 65 | fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result 66 | where 67 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 68 | H::OutputSize: IsLess + IsLessOrEqual, 69 | { 70 | ::hash_to_scalar::>(input, dst) 71 | .map_err(|_| InternalError::Input) 72 | } 73 | 74 | fn base_elem() -> Self::Elem { 75 | ProjectivePoint::::generator() 76 | } 77 | 78 | fn identity_elem() -> Self::Elem { 79 | ProjectivePoint::::identity() 80 | } 81 | 82 | fn serialize_elem(elem: Self::Elem) -> GenericArray { 83 | let bytes = elem.to_encoded_point(true); 84 | let bytes = bytes.as_bytes(); 85 | let mut result = GenericArray::default(); 86 | result[..bytes.len()].copy_from_slice(bytes); 87 | result 88 | } 89 | 90 | fn deserialize_elem(element_bits: &[u8]) -> Result { 91 | PublicKey::::from_sec1_bytes(element_bits) 92 | .map(|public_key| public_key.to_projective()) 93 | .map_err(|_| Error::Deserialization) 94 | } 95 | 96 | fn random_scalar(rng: &mut R) -> Self::Scalar { 97 | *SecretKey::::random(rng).to_nonzero_scalar() 98 | } 99 | 100 | fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar { 101 | Option::from(scalar.invert()).unwrap() 102 | } 103 | 104 | fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice { 105 | scalar.is_zero() 106 | } 107 | 108 | #[cfg(test)] 109 | fn zero_scalar() -> Self::Scalar { 110 | Scalar::::ZERO 111 | } 112 | 113 | fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { 114 | let bytes: FieldBytes = scalar.into(); 115 | let mut result = GenericArray::::default(); 116 | result.as_mut_slice().copy_from_slice(bytes.as_ref()); 117 | result 118 | } 119 | 120 | fn deserialize_scalar(scalar_bits: &[u8]) -> Result { 121 | SecretKey::::from_slice(scalar_bits) 122 | .map(|secret_key| *secret_key.to_nonzero_scalar()) 123 | .map_err(|_| Error::Deserialization) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/group/ristretto.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; 10 | use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; 11 | use curve25519_dalek::scalar::Scalar; 12 | use curve25519_dalek::traits::Identity; 13 | use digest::core_api::BlockSizeUser; 14 | use digest::{FixedOutput, HashMarker}; 15 | use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; 16 | use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; 17 | use generic_array::GenericArray; 18 | use rand_core::{CryptoRng, RngCore}; 19 | use subtle::ConstantTimeEq; 20 | 21 | use super::Group; 22 | use crate::{Error, InternalError, Result}; 23 | 24 | /// [`Group`] implementation for Ristretto255. 25 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 26 | pub struct Ristretto255; 27 | 28 | #[cfg(feature = "ristretto255-ciphersuite")] 29 | impl crate::CipherSuite for Ristretto255 { 30 | const ID: &'static str = "ristretto255-SHA512"; 31 | 32 | type Group = Ristretto255; 33 | 34 | type Hash = sha2::Sha512; 35 | } 36 | 37 | impl Group for Ristretto255 { 38 | type Elem = RistrettoPoint; 39 | 40 | type ElemLen = U32; 41 | 42 | type Scalar = Scalar; 43 | 44 | type ScalarLen = U32; 45 | 46 | // Implements the `hash_to_ristretto255()` function from 47 | // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-B 48 | fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result 49 | where 50 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 51 | H::OutputSize: IsLess + IsLessOrEqual, 52 | { 53 | let mut uniform_bytes = GenericArray::<_, U64>::default(); 54 | ExpandMsgXmd::::expand_message(input, dst, 64) 55 | .map_err(|_| InternalError::Input)? 56 | .fill_bytes(&mut uniform_bytes); 57 | 58 | Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes.into())) 59 | } 60 | 61 | // Implements the `HashToScalar()` function from 62 | // https://www.rfc-editor.org/rfc/rfc9497#section-4.1 63 | fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result 64 | where 65 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 66 | H::OutputSize: IsLess + IsLessOrEqual, 67 | { 68 | let mut uniform_bytes = GenericArray::<_, U64>::default(); 69 | ExpandMsgXmd::::expand_message(input, dst, 64) 70 | .map_err(|_| InternalError::Input)? 71 | .fill_bytes(&mut uniform_bytes); 72 | 73 | Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes.into())) 74 | } 75 | 76 | fn base_elem() -> Self::Elem { 77 | RISTRETTO_BASEPOINT_POINT 78 | } 79 | 80 | fn identity_elem() -> Self::Elem { 81 | RistrettoPoint::identity() 82 | } 83 | 84 | // serialization of a group element 85 | fn serialize_elem(elem: Self::Elem) -> GenericArray { 86 | elem.compress().to_bytes().into() 87 | } 88 | 89 | fn deserialize_elem(element_bits: &[u8]) -> Result { 90 | CompressedRistretto::from_slice(element_bits) 91 | .map_err(|_| Error::Deserialization)? 92 | .decompress() 93 | .filter(|point| point != &RistrettoPoint::identity()) 94 | .ok_or(Error::Deserialization) 95 | } 96 | 97 | fn random_scalar(rng: &mut R) -> Self::Scalar { 98 | loop { 99 | let mut scalar_bytes = [0u8; 32]; 100 | rng.fill_bytes(&mut scalar_bytes); 101 | 102 | if let Ok(scalar) = Self::deserialize_scalar(&scalar_bytes) { 103 | break scalar; 104 | } 105 | } 106 | } 107 | 108 | fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar { 109 | scalar.invert() 110 | } 111 | 112 | fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice { 113 | scalar.ct_eq(&Scalar::ZERO) 114 | } 115 | 116 | #[cfg(test)] 117 | fn zero_scalar() -> Self::Scalar { 118 | Scalar::ZERO 119 | } 120 | 121 | fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { 122 | scalar.to_bytes().into() 123 | } 124 | 125 | fn deserialize_scalar(scalar_bits: &[u8]) -> Result { 126 | scalar_bits 127 | .try_into() 128 | .ok() 129 | .and_then(|bytes| Scalar::from_canonical_bytes(bytes).into()) 130 | .filter(|scalar| scalar != &Scalar::ZERO) 131 | .ok_or(Error::Deserialization) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: [opened, reopened, synchronize] 8 | 9 | jobs: 10 | cargo-audit: 11 | name: Audit 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Cache cargo-audit 15 | uses: actions/cache@v4 16 | with: 17 | path: | 18 | ~/.cargo/.crates.toml 19 | ~/.cargo/.crates2.json 20 | ~/.cargo/bin/cargo-audit 21 | key: cargo-audit 22 | 23 | - name: Install cargo-audit 24 | run: cargo install cargo-audit 25 | 26 | - name: Checkout sources 27 | uses: actions/checkout@v4 28 | 29 | - name: Run cargo audit 30 | run: cargo audit -D warnings 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | backend_feature: 38 | - --features ristretto255-ciphersuite 39 | - 40 | frontend_feature: 41 | - 42 | - --features danger 43 | - --features serde 44 | toolchain: 45 | - stable 46 | - 1.83.0 47 | name: test 48 | steps: 49 | - name: Checkout sources 50 | uses: actions/checkout@v4 51 | 52 | - name: Install ${{ matrix.toolchain }} toolchain 53 | uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: ${{ matrix.toolchain }} 57 | override: true 58 | 59 | - name: Run cargo test 60 | uses: actions-rs/cargo@v1 61 | with: 62 | command: test 63 | args: --no-default-features ${{ matrix.backend_feature }} 64 | 65 | - name: Run cargo test with alloc 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: test 69 | args: --no-default-features ${{ matrix.frontend_feature }},alloc ${{ matrix.backend_feature }} 70 | 71 | - name: Run cargo test with std 72 | uses: actions-rs/cargo@v1 73 | with: 74 | command: test 75 | args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }} 76 | 77 | - name: Run cargo test with all features enabled 78 | uses: actions-rs/cargo@v1 79 | with: 80 | command: test 81 | args: --all-features 82 | 83 | build-no-std: 84 | name: Build with no-std on ${{ matrix.target }} 85 | runs-on: ubuntu-latest 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | target: 90 | # for wasm 91 | - wasm32-unknown-unknown 92 | # for any no_std target 93 | - thumbv6m-none-eabi 94 | backend_feature: 95 | - 96 | - --features ristretto255-ciphersuite 97 | frontend_feature: 98 | - 99 | - --features danger 100 | - --features serde 101 | steps: 102 | - uses: actions/checkout@v4 103 | - uses: hecrj/setup-rust-action@v2 104 | - run: rustup target add ${{ matrix.target }} 105 | - run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} 106 | 107 | 108 | clippy: 109 | name: cargo clippy 110 | runs-on: ubuntu-latest 111 | steps: 112 | - name: Checkout sources 113 | uses: actions/checkout@v4 114 | 115 | - name: Install stable toolchain 116 | uses: actions-rs/toolchain@v1 117 | with: 118 | profile: minimal 119 | toolchain: stable 120 | override: true 121 | components: clippy 122 | 123 | - name: Run cargo clippy 124 | uses: actions-rs/cargo@v1 125 | with: 126 | command: clippy 127 | args: --all-features --all-targets -- -D warnings 128 | 129 | - name: Run cargo doc 130 | uses: actions-rs/cargo@v1 131 | env: 132 | RUSTDOCFLAGS: -D warnings 133 | with: 134 | command: doc 135 | args: --no-deps --document-private-items --features danger,std 136 | 137 | 138 | rustfmt: 139 | name: cargo fmt 140 | runs-on: ubuntu-latest 141 | steps: 142 | - name: Checkout sources 143 | uses: actions/checkout@v4 144 | 145 | - name: Install nightly toolchain 146 | uses: actions-rs/toolchain@v1 147 | with: 148 | profile: minimal 149 | toolchain: nightly 150 | override: true 151 | components: rustfmt 152 | 153 | - name: Run cargo fmt 154 | uses: actions-rs/cargo@v1 155 | with: 156 | command: fmt 157 | args: --all -- --check 158 | 159 | taplo: 160 | name: Taplo 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: Cache 164 | uses: actions/cache@v4 165 | with: 166 | path: | 167 | ~/.cargo/.crates.toml 168 | ~/.cargo/.crates2.json 169 | ~/.cargo/bin/taplo 170 | key: taplo 171 | 172 | - name: Install Taplo 173 | run: cargo install taplo-cli --locked 174 | 175 | - name: Checkout sources 176 | uses: actions/checkout@v4 177 | 178 | - name: Run Taplo 179 | run: taplo fmt --check 180 | -------------------------------------------------------------------------------- /src/group/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Defines the Group trait to specify the underlying prime order group 10 | 11 | mod elliptic_curve; 12 | #[cfg(feature = "ristretto255")] 13 | mod ristretto; 14 | 15 | use core::ops::{Add, Mul, Sub}; 16 | 17 | use digest::core_api::BlockSizeUser; 18 | use digest::{FixedOutput, HashMarker}; 19 | use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; 20 | use generic_array::{ArrayLength, GenericArray}; 21 | use rand_core::{CryptoRng, RngCore}; 22 | #[cfg(feature = "ristretto255")] 23 | pub use ristretto::Ristretto255; 24 | use subtle::{Choice, ConstantTimeEq}; 25 | use zeroize::Zeroize; 26 | 27 | use crate::{InternalError, Result}; 28 | 29 | /// A prime-order subgroup of a base field (EC, prime-order field ...). This 30 | /// subgroup is noted additively — as in the RFC — in this trait. 31 | pub trait Group 32 | where 33 | // `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen` 34 | Self::ScalarLen: Add, 35 | Sum: ArrayLength, 36 | // `ProofLen` 37 | Self::ScalarLen: Add, 38 | Sum: ArrayLength, 39 | { 40 | /// The type of group elements 41 | type Elem: ConstantTimeEq 42 | + Copy 43 | + Zeroize 44 | + for<'a> Add<&'a Self::Elem, Output = Self::Elem> 45 | + for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>; 46 | 47 | /// The byte length necessary to represent group elements 48 | type ElemLen: ArrayLength + 'static; 49 | 50 | /// The type of base field scalars 51 | type Scalar: ConstantTimeEq 52 | + Copy 53 | + Zeroize 54 | + for<'a> Add<&'a Self::Scalar, Output = Self::Scalar> 55 | + for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar> 56 | + for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>; 57 | 58 | /// The byte length necessary to represent scalars 59 | type ScalarLen: ArrayLength + 'static; 60 | 61 | /// Transforms a password and domain separation tag (DST) into a curve point 62 | /// 63 | /// # Errors 64 | /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer 65 | /// then [`u16::MAX`]. 66 | fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result 67 | where 68 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 69 | H::OutputSize: IsLess + IsLessOrEqual; 70 | 71 | /// Hashes a slice of pseudo-random bytes to a scalar 72 | /// 73 | /// # Errors 74 | /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer 75 | /// then [`u16::MAX`]. 76 | fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result 77 | where 78 | H: BlockSizeUser + Default + FixedOutput + HashMarker, 79 | H::OutputSize: IsLess + IsLessOrEqual; 80 | 81 | /// Get the base point for the group 82 | fn base_elem() -> Self::Elem; 83 | 84 | /// Returns the identity group element 85 | fn identity_elem() -> Self::Elem; 86 | 87 | /// Returns `true` if the element is equal to the identity element 88 | fn is_identity_elem(elem: Self::Elem) -> Choice { 89 | Self::identity_elem().ct_eq(&elem) 90 | } 91 | 92 | /// Serializes the `self` group element 93 | fn serialize_elem(elem: Self::Elem) -> GenericArray; 94 | 95 | /// Return an element from its fixed-length bytes representation. If the 96 | /// element is the identity element, return an error. 97 | /// 98 | /// # Errors 99 | /// [`Error::Deserialization`](crate::Error::Deserialization) if the element 100 | /// is not a valid point on the group or the identity element. 101 | fn deserialize_elem(element_bits: &[u8]) -> Result; 102 | 103 | /// picks a scalar at random 104 | fn random_scalar(rng: &mut R) -> Self::Scalar; 105 | 106 | /// The multiplicative inverse of this scalar 107 | fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar; 108 | 109 | /// Returns `true` if the scalar is zero. 110 | fn is_zero_scalar(scalar: Self::Scalar) -> Choice; 111 | 112 | /// Returns the scalar representing zero 113 | #[cfg(test)] 114 | fn zero_scalar() -> Self::Scalar; 115 | 116 | /// Serializes a scalar to bytes 117 | fn serialize_scalar(scalar: Self::Scalar) -> GenericArray; 118 | 119 | /// Return a scalar from its fixed-length bytes representation. If the 120 | /// scalar is zero or invalid, then return an error. 121 | /// 122 | /// # Errors 123 | /// [`Error::Deserialization`](crate::Error::Deserialization) if the scalar 124 | /// is not a valid point on the group or zero. 125 | fn deserialize_scalar(scalar_bits: &[u8]) -> Result; 126 | } 127 | 128 | #[cfg(test)] 129 | mod tests; 130 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Handles the serialization of each of the components used in the VOPRF 10 | //! protocol 11 | 12 | use generic_array::sequence::Concat; 13 | use generic_array::typenum::{Sum, Unsigned}; 14 | use generic_array::GenericArray; 15 | 16 | use crate::{ 17 | BlindedElement, CipherSuite, Error, EvaluationElement, Group, OprfClient, OprfServer, 18 | PoprfClient, PoprfServer, Proof, Result, VoprfClient, VoprfServer, 19 | }; 20 | 21 | ////////////////////////////////////////////////////////// 22 | // Serialization and Deserialization for High-Level API // 23 | // ==================================================== // 24 | ////////////////////////////////////////////////////////// 25 | 26 | /// Length of [`OprfClient`] in bytes for serialization. 27 | pub type OprfClientLen = <::Group as Group>::ScalarLen; 28 | 29 | impl OprfClient { 30 | /// Serialization into bytes 31 | pub fn serialize(&self) -> GenericArray> { 32 | CS::Group::serialize_scalar(self.blind) 33 | } 34 | 35 | /// Deserialization from bytes 36 | /// 37 | /// # Errors 38 | /// [`Error::Deserialization`] if failed to deserialize `input`. 39 | pub fn deserialize(mut input: &[u8]) -> Result { 40 | let blind = deserialize_scalar::(&mut input)?; 41 | 42 | Ok(Self { blind }) 43 | } 44 | } 45 | 46 | /// Length of [`VoprfClient`] in bytes for serialization. 47 | pub type VoprfClientLen = Sum< 48 | <::Group as Group>::ScalarLen, 49 | <::Group as Group>::ElemLen, 50 | >; 51 | 52 | impl VoprfClient { 53 | /// Serialization into bytes 54 | pub fn serialize(&self) -> GenericArray> { 55 | ::serialize_scalar(self.blind) 56 | .concat(::serialize_elem(self.blinded_element)) 57 | } 58 | 59 | /// Deserialization from bytes 60 | /// 61 | /// # Errors 62 | /// [`Error::Deserialization`] if failed to deserialize `input`. 63 | pub fn deserialize(mut input: &[u8]) -> Result { 64 | let blind = deserialize_scalar::(&mut input)?; 65 | let blinded_element = deserialize_elem::(&mut input)?; 66 | 67 | Ok(Self { 68 | blind, 69 | blinded_element, 70 | }) 71 | } 72 | } 73 | 74 | /// Length of [`PoprfClient`] in bytes for serialization. 75 | pub type PoprfClientLen = Sum< 76 | <::Group as Group>::ScalarLen, 77 | <::Group as Group>::ElemLen, 78 | >; 79 | 80 | impl PoprfClient { 81 | /// Serialization into bytes 82 | pub fn serialize(&self) -> GenericArray> { 83 | ::serialize_scalar(self.blind) 84 | .concat(::serialize_elem(self.blinded_element)) 85 | } 86 | 87 | /// Deserialization from bytes 88 | /// 89 | /// # Errors 90 | /// [`Error::Deserialization`] if failed to deserialize `input`. 91 | pub fn deserialize(mut input: &[u8]) -> Result { 92 | let blind = deserialize_scalar::(&mut input)?; 93 | let blinded_element = deserialize_elem::(&mut input)?; 94 | 95 | Ok(Self { 96 | blind, 97 | blinded_element, 98 | }) 99 | } 100 | } 101 | 102 | /// Length of [`OprfServer`] in bytes for serialization. 103 | pub type OprfServerLen = <::Group as Group>::ScalarLen; 104 | 105 | impl OprfServer { 106 | /// Serialization into bytes 107 | pub fn serialize(&self) -> GenericArray> { 108 | CS::Group::serialize_scalar(self.sk) 109 | } 110 | 111 | /// Deserialization from bytes 112 | /// 113 | /// # Errors 114 | /// [`Error::Deserialization`] if failed to deserialize `input`. 115 | pub fn deserialize(mut input: &[u8]) -> Result { 116 | let sk = deserialize_scalar::(&mut input)?; 117 | 118 | Ok(Self { sk }) 119 | } 120 | } 121 | 122 | /// Length of [`VoprfServer`] in bytes for serialization. 123 | pub type VoprfServerLen = Sum< 124 | <::Group as Group>::ScalarLen, 125 | <::Group as Group>::ElemLen, 126 | >; 127 | 128 | impl VoprfServer { 129 | /// Serialization into bytes 130 | pub fn serialize(&self) -> GenericArray> { 131 | CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk)) 132 | } 133 | 134 | /// Deserialization from bytes 135 | /// 136 | /// # Errors 137 | /// [`Error::Deserialization`] if failed to deserialize `input`. 138 | pub fn deserialize(mut input: &[u8]) -> Result { 139 | let sk = deserialize_scalar::(&mut input)?; 140 | let pk = deserialize_elem::(&mut input)?; 141 | 142 | Ok(Self { sk, pk }) 143 | } 144 | } 145 | 146 | /// Length of [`PoprfServer`] in bytes for serialization. 147 | pub type PoprfServerLen = Sum< 148 | <::Group as Group>::ScalarLen, 149 | <::Group as Group>::ElemLen, 150 | >; 151 | 152 | impl PoprfServer { 153 | /// Serialization into bytes 154 | pub fn serialize(&self) -> GenericArray> { 155 | CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk)) 156 | } 157 | 158 | /// Deserialization from bytes 159 | /// 160 | /// # Errors 161 | /// [`Error::Deserialization`] if failed to deserialize `input`. 162 | pub fn deserialize(mut input: &[u8]) -> Result { 163 | let sk = deserialize_scalar::(&mut input)?; 164 | let pk = deserialize_elem::(&mut input)?; 165 | 166 | Ok(Self { sk, pk }) 167 | } 168 | } 169 | 170 | /// Length of [`Proof`] in bytes for serialization. 171 | pub type ProofLen = Sum< 172 | <::Group as Group>::ScalarLen, 173 | <::Group as Group>::ScalarLen, 174 | >; 175 | 176 | impl Proof { 177 | /// Serialization into bytes 178 | pub fn serialize(&self) -> GenericArray> { 179 | CS::Group::serialize_scalar(self.c_scalar) 180 | .concat(CS::Group::serialize_scalar(self.s_scalar)) 181 | } 182 | 183 | /// Deserialization from bytes 184 | /// 185 | /// # Errors 186 | /// [`Error::Deserialization`] if failed to deserialize `input`. 187 | pub fn deserialize(mut input: &[u8]) -> Result { 188 | let c_scalar = deserialize_scalar::(&mut input)?; 189 | let s_scalar = deserialize_scalar::(&mut input)?; 190 | 191 | Ok(Proof { c_scalar, s_scalar }) 192 | } 193 | } 194 | 195 | /// Length of [`BlindedElement`] in bytes for serialization. 196 | pub type BlindedElementLen = <::Group as Group>::ElemLen; 197 | 198 | impl BlindedElement { 199 | /// Serialization into bytes 200 | pub fn serialize(&self) -> GenericArray> { 201 | CS::Group::serialize_elem(self.0) 202 | } 203 | 204 | /// Deserialization from bytes 205 | /// 206 | /// # Errors 207 | /// [`Error::Deserialization`] if failed to deserialize `input`. 208 | pub fn deserialize(mut input: &[u8]) -> Result { 209 | let value = deserialize_elem::(&mut input)?; 210 | 211 | Ok(Self(value)) 212 | } 213 | } 214 | 215 | /// Length of [`EvaluationElement`] in bytes for serialization. 216 | pub type EvaluationElementLen = <::Group as Group>::ElemLen; 217 | 218 | impl EvaluationElement { 219 | /// Serialization into bytes 220 | pub fn serialize(&self) -> GenericArray> { 221 | CS::Group::serialize_elem(self.0) 222 | } 223 | 224 | /// Deserialization from bytes 225 | /// 226 | /// # Errors 227 | /// [`Error::Deserialization`] if failed to deserialize `input`. 228 | pub fn deserialize(mut input: &[u8]) -> Result { 229 | let value = deserialize_elem::(&mut input)?; 230 | 231 | Ok(Self(value)) 232 | } 233 | } 234 | 235 | fn deserialize_elem(input: &mut &[u8]) -> Result { 236 | let input = input 237 | .take_ext(G::ElemLen::USIZE) 238 | .ok_or(Error::Deserialization)?; 239 | G::deserialize_elem(input) 240 | } 241 | 242 | fn deserialize_scalar(input: &mut &[u8]) -> Result { 243 | let input = input 244 | .take_ext(G::ScalarLen::USIZE) 245 | .ok_or(Error::Deserialization)?; 246 | G::deserialize_scalar(input) 247 | } 248 | 249 | trait SliceExt { 250 | fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self>; 251 | } 252 | 253 | impl SliceExt for [T] { 254 | fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self> { 255 | if take > self.len() { 256 | return None; 257 | } 258 | 259 | let (front, back) = self.split_at(take); 260 | *self = back; 261 | Some(front) 262 | } 263 | } 264 | 265 | #[cfg(feature = "serde")] 266 | pub(crate) mod serde { 267 | use core::marker::PhantomData; 268 | 269 | use generic_array::GenericArray; 270 | use serde::de::{Deserializer, Error}; 271 | use serde::ser::Serializer; 272 | use serde::{Deserialize, Serialize}; 273 | 274 | use crate::Group; 275 | 276 | pub(crate) struct Element(PhantomData); 277 | 278 | impl<'de, G: Group> Element { 279 | pub(crate) fn deserialize(deserializer: D) -> Result 280 | where 281 | D: Deserializer<'de>, 282 | { 283 | GenericArray::<_, G::ElemLen>::deserialize(deserializer) 284 | .and_then(|bytes| G::deserialize_elem(&bytes).map_err(D::Error::custom)) 285 | } 286 | 287 | pub(crate) fn serialize(self_: &G::Elem, serializer: S) -> Result 288 | where 289 | S: Serializer, 290 | { 291 | G::serialize_elem(*self_).serialize(serializer) 292 | } 293 | } 294 | 295 | pub(crate) struct Scalar(PhantomData); 296 | 297 | impl<'de, G: Group> Scalar { 298 | pub(crate) fn deserialize(deserializer: D) -> Result 299 | where 300 | D: Deserializer<'de>, 301 | { 302 | GenericArray::<_, G::ScalarLen>::deserialize(deserializer) 303 | .and_then(|bytes| G::deserialize_scalar(&bytes).map_err(D::Error::custom)) 304 | } 305 | 306 | pub(crate) fn serialize(self_: &G::Scalar, serializer: S) -> Result 307 | where 308 | S: Serializer, 309 | { 310 | G::serialize_scalar(*self_).serialize(serializer) 311 | } 312 | } 313 | } 314 | 315 | #[cfg(test)] 316 | mod test { 317 | use proptest::collection::vec; 318 | use proptest::prelude::*; 319 | 320 | use crate::{ 321 | BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof, 322 | VoprfClient, VoprfServer, 323 | }; 324 | 325 | macro_rules! test_deserialize { 326 | ($item:ident, $bytes:ident) => { 327 | #[cfg(feature = "ristretto255")] 328 | { 329 | let _ = $item::::deserialize(&$bytes[..]); 330 | } 331 | 332 | let _ = $item::::deserialize(&$bytes[..]); 333 | let _ = $item::::deserialize(&$bytes[..]); 334 | let _ = $item::::deserialize(&$bytes[..]); 335 | }; 336 | } 337 | 338 | proptest! { 339 | #[test] 340 | fn test_nocrash_oprf_client(bytes in vec(any::(), 0..200)) { 341 | test_deserialize!(OprfClient, bytes); 342 | } 343 | 344 | #[test] 345 | fn test_nocrash_voprf_client(bytes in vec(any::(), 0..200)) { 346 | test_deserialize!(VoprfClient, bytes); 347 | } 348 | 349 | #[test] 350 | fn test_nocrash_poprf_client(bytes in vec(any::(), 0..200)) { 351 | test_deserialize!(PoprfClient, bytes); 352 | } 353 | 354 | #[test] 355 | fn test_nocrash_oprf_server(bytes in vec(any::(), 0..200)) { 356 | test_deserialize!(OprfServer, bytes); 357 | } 358 | 359 | #[test] 360 | fn test_nocrash_voprf_server(bytes in vec(any::(), 0..200)) { 361 | test_deserialize!(VoprfServer, bytes); 362 | } 363 | 364 | #[test] 365 | fn test_nocrash_poprf_server(bytes in vec(any::(), 0..200)) { 366 | test_deserialize!(PoprfServer, bytes); 367 | } 368 | 369 | 370 | #[test] 371 | fn test_nocrash_blinded_element(bytes in vec(any::(), 0..200)) { 372 | test_deserialize!(BlindedElement, bytes); 373 | } 374 | 375 | #[test] 376 | fn test_nocrash_evaluation_element(bytes in vec(any::(), 0..200)) { 377 | test_deserialize!(EvaluationElement, bytes); 378 | } 379 | 380 | #[test] 381 | fn test_nocrash_proof(bytes in vec(any::(), 0..200)) { 382 | test_deserialize!(Proof, bytes); 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/oprf.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Contains the main OPRF API 10 | 11 | use core::iter::{self, Map}; 12 | 13 | use derive_where::derive_where; 14 | use digest::{Digest, Output}; 15 | use generic_array::typenum::Unsigned; 16 | use generic_array::GenericArray; 17 | use rand_core::{CryptoRng, RngCore}; 18 | 19 | use crate::common::{ 20 | derive_key_internal, deterministic_blind_unchecked, hash_to_group, i2osp_2, 21 | server_evaluate_hash_input, BlindedElement, EvaluationElement, Mode, STR_FINALIZE, 22 | }; 23 | #[cfg(feature = "serde")] 24 | use crate::serialization::serde::Scalar; 25 | use crate::{CipherSuite, Error, Group, Result}; 26 | 27 | /////////////// 28 | // Constants // 29 | // ========= // 30 | /////////////// 31 | 32 | //////////////////////////// 33 | // High-level API Structs // 34 | // ====================== // 35 | //////////////////////////// 36 | 37 | /// A client which engages with a [OprfServer] in base mode, meaning 38 | /// that the OPRF outputs are not verifiable. 39 | #[derive_where(Clone, ZeroizeOnDrop)] 40 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar)] 41 | #[cfg_attr( 42 | feature = "serde", 43 | derive(serde::Deserialize, serde::Serialize), 44 | serde(bound = "") 45 | )] 46 | pub struct OprfClient { 47 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 48 | pub(crate) blind: ::Scalar, 49 | } 50 | 51 | /// A server which engages with a [OprfClient] in base mode, meaning 52 | /// that the OPRF outputs are not verifiable. 53 | #[derive_where(Clone, ZeroizeOnDrop)] 54 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar)] 55 | #[cfg_attr( 56 | feature = "serde", 57 | derive(serde::Deserialize, serde::Serialize), 58 | serde(bound = "") 59 | )] 60 | pub struct OprfServer { 61 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 62 | pub(crate) sk: ::Scalar, 63 | } 64 | 65 | ///////////////////////// 66 | // API Implementations // 67 | // =================== // 68 | ///////////////////////// 69 | 70 | impl OprfClient { 71 | /// Computes the first step for the multiplicative blinding version of 72 | /// DH-OPRF. 73 | /// 74 | /// # Errors 75 | /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 76 | pub fn blind( 77 | input: &[u8], 78 | blinding_factor_rng: &mut R, 79 | ) -> Result> { 80 | let blind = CS::Group::random_scalar(blinding_factor_rng); 81 | Self::deterministic_blind_unchecked_inner(input, blind) 82 | } 83 | 84 | /// Computes the first step for the multiplicative blinding version of 85 | /// DH-OPRF, taking a blinding factor scalar as input instead of sampling 86 | /// from an RNG. 87 | /// 88 | /// # Caution 89 | /// 90 | /// This should be used with caution, since it does not perform any checks 91 | /// on the validity of the blinding factor! 92 | /// 93 | /// # Errors 94 | /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 95 | #[cfg(any(feature = "danger", test))] 96 | pub fn deterministic_blind_unchecked( 97 | input: &[u8], 98 | blind: ::Scalar, 99 | ) -> Result> { 100 | Self::deterministic_blind_unchecked_inner(input, blind) 101 | } 102 | 103 | /// Can only fail with [`Error::Input`]. 104 | fn deterministic_blind_unchecked_inner( 105 | input: &[u8], 106 | blind: ::Scalar, 107 | ) -> Result> { 108 | let blinded_element = deterministic_blind_unchecked::(input, &blind, Mode::Oprf)?; 109 | Ok(OprfClientBlindResult { 110 | state: Self { blind }, 111 | message: BlindedElement(blinded_element), 112 | }) 113 | } 114 | 115 | /// Computes the third step for the multiplicative blinding version of 116 | /// DH-OPRF, in which the client unblinds the server's message. 117 | /// 118 | /// # Errors 119 | /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 120 | pub fn finalize( 121 | &self, 122 | input: &[u8], 123 | evaluation_element: &EvaluationElement, 124 | ) -> Result> { 125 | let unblinded_element = evaluation_element.0 * &CS::Group::invert_scalar(self.blind); 126 | let mut outputs = 127 | finalize_after_unblind::(iter::once((input, unblinded_element)), &[]); 128 | outputs.next().unwrap() 129 | } 130 | 131 | /// Only used for test functions 132 | #[cfg(test)] 133 | pub fn from_blind(blind: ::Scalar) -> Self { 134 | Self { blind } 135 | } 136 | 137 | /// Exposes the blind group element 138 | #[cfg(feature = "danger")] 139 | pub fn get_blind(&self) -> ::Scalar { 140 | self.blind 141 | } 142 | } 143 | 144 | impl OprfServer { 145 | /// Produces a new instance of a [OprfServer] using a supplied RNG 146 | /// 147 | /// # Errors 148 | /// [`Error::Protocol`] if the protocol fails and can't be completed. 149 | pub fn new(rng: &mut R) -> Result { 150 | let mut seed = GenericArray::<_, ::ScalarLen>::default(); 151 | rng.fill_bytes(&mut seed); 152 | Self::new_from_seed(&seed, &[]) 153 | } 154 | 155 | /// Produces a new instance of a [OprfServer] using a supplied set 156 | /// of bytes to represent the server's private key 157 | /// 158 | /// # Errors 159 | /// [`Error::Deserialization`] if the private key is not a valid point on 160 | /// the group or zero. 161 | pub fn new_with_key(private_key_bytes: &[u8]) -> Result { 162 | let sk = CS::Group::deserialize_scalar(private_key_bytes)?; 163 | Ok(Self { sk }) 164 | } 165 | 166 | /// Produces a new instance of a [OprfServer] using a supplied set 167 | /// of bytes which are used as a seed to derive the server's private key. 168 | /// 169 | /// Corresponds to DeriveKeyPair() function from the VOPRF specification. 170 | /// 171 | /// # Errors 172 | /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer 173 | /// then `u16::MAX - 3`. 174 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 175 | pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result { 176 | let sk = derive_key_internal::(seed, info, Mode::Oprf)?; 177 | Ok(Self { sk }) 178 | } 179 | 180 | /// Only used for tests 181 | #[cfg(test)] 182 | pub fn get_private_key(&self) -> ::Scalar { 183 | self.sk 184 | } 185 | 186 | /// Computes the second step for the multiplicative blinding version of 187 | /// DH-OPRF. This message is sent from the server (who holds the OPRF key) 188 | /// to the client. 189 | pub fn blind_evaluate(&self, blinded_element: &BlindedElement) -> EvaluationElement { 190 | EvaluationElement(blinded_element.0 * &self.sk) 191 | } 192 | 193 | /// Computes the output of the OPRF on the server side 194 | /// 195 | /// # Errors 196 | /// [`Error::Input`] if the `input` is longer then [`u16::MAX`]. 197 | pub fn evaluate(&self, input: &[u8]) -> Result::Hash>> { 198 | let input_element = hash_to_group::(input, Mode::Oprf)?; 199 | if CS::Group::is_identity_elem(input_element).into() { 200 | return Err(Error::Input); 201 | }; 202 | let evaluated_element = input_element * &self.sk; 203 | 204 | let issued_element = CS::Group::serialize_elem(evaluated_element); 205 | 206 | server_evaluate_hash_input::(input, None, issued_element) 207 | } 208 | } 209 | 210 | ///////////////////////// 211 | // Convenience Structs // 212 | //==================== // 213 | ///////////////////////// 214 | 215 | /// Contains the fields that are returned by a non-verifiable client blind 216 | #[derive_where(Debug; ::Scalar, ::Elem)] 217 | pub struct OprfClientBlindResult { 218 | /// The state to be persisted on the client 219 | pub state: OprfClient, 220 | /// The message to send to the server 221 | pub message: BlindedElement, 222 | } 223 | 224 | ///////////////////// 225 | // Inner functions // 226 | // =============== // 227 | ///////////////////// 228 | 229 | type FinalizeAfterUnblindResult<'a, C, I, IE> = Map< 230 | IE, 231 | fn((I, <::Group as Group>::Elem)) -> Result::Hash>>, 232 | >; 233 | 234 | /// Returned values can only fail with [`Error::Input`]. 235 | fn finalize_after_unblind< 236 | 'a, 237 | CS: CipherSuite, 238 | I: AsRef<[u8]>, 239 | IE: 'a + Iterator::Elem)>, 240 | >( 241 | inputs_and_unblinded_elements: IE, 242 | _unused: &'a [u8], 243 | ) -> FinalizeAfterUnblindResult<'a, CS, I, IE> { 244 | inputs_and_unblinded_elements.map(|(input, unblinded_element)| { 245 | let elem_len = ::ElemLen::U16.to_be_bytes(); 246 | 247 | // hashInput = I2OSP(len(input), 2) || input || 248 | // I2OSP(len(unblindedElement), 2) || unblindedElement || 249 | // "Finalize" 250 | // return Hash(hashInput) 251 | Ok(CS::Hash::new() 252 | .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?) 253 | .chain_update(input.as_ref()) 254 | .chain_update(elem_len) 255 | .chain_update(CS::Group::serialize_elem(unblinded_element)) 256 | .chain_update(STR_FINALIZE) 257 | .finalize()) 258 | }) 259 | } 260 | 261 | /////////// 262 | // Tests // 263 | // ===== // 264 | /////////// 265 | 266 | #[cfg(test)] 267 | mod tests { 268 | use core::ptr; 269 | 270 | use rand::rngs::OsRng; 271 | 272 | use super::*; 273 | use crate::common::{Dst, STR_HASH_TO_GROUP}; 274 | use crate::Group; 275 | 276 | fn prf( 277 | input: &[u8], 278 | key: ::Scalar, 279 | info: &[u8], 280 | mode: Mode, 281 | ) -> Output { 282 | let dst = Dst::new::(STR_HASH_TO_GROUP, mode); 283 | let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); 284 | 285 | let res = point * &key; 286 | 287 | finalize_after_unblind::(iter::once((input, res)), info) 288 | .next() 289 | .unwrap() 290 | .unwrap() 291 | } 292 | 293 | fn base_retrieval() { 294 | let input = b"input"; 295 | let mut rng = OsRng; 296 | let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); 297 | let server = OprfServer::::new(&mut rng).unwrap(); 298 | let message = server.blind_evaluate(&client_blind_result.message); 299 | let client_finalize_result = client_blind_result.state.finalize(input, &message).unwrap(); 300 | let res2 = prf::(input, server.get_private_key(), &[], Mode::Oprf); 301 | assert_eq!(client_finalize_result, res2); 302 | } 303 | 304 | fn base_inversion_unsalted() { 305 | let mut rng = OsRng; 306 | let mut input = [0u8; 64]; 307 | rng.fill_bytes(&mut input); 308 | let client_blind_result = OprfClient::::blind(&input, &mut rng).unwrap(); 309 | let client_finalize_result = client_blind_result 310 | .state 311 | .finalize(&input, &EvaluationElement(client_blind_result.message.0)) 312 | .unwrap(); 313 | 314 | let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); 315 | let point = CS::Group::hash_to_curve::(&[&input], &dst.as_dst()).unwrap(); 316 | let res2 = finalize_after_unblind::(iter::once((input.as_ref(), point)), &[]) 317 | .next() 318 | .unwrap() 319 | .unwrap(); 320 | 321 | assert_eq!(client_finalize_result, res2); 322 | } 323 | 324 | fn server_evaluate() { 325 | let input = b"input"; 326 | let mut rng = OsRng; 327 | let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); 328 | let server = OprfServer::::new(&mut rng).unwrap(); 329 | let server_result = server.blind_evaluate(&client_blind_result.message); 330 | 331 | let client_finalize = client_blind_result 332 | .state 333 | .finalize(input, &server_result) 334 | .unwrap(); 335 | 336 | // We expect the outputs from client and server to be equal given an identical 337 | // input 338 | let server_evaluate = server.evaluate(input).unwrap(); 339 | assert_eq!(client_finalize, server_evaluate); 340 | 341 | // We expect the outputs from client and server to be different given different 342 | // inputs 343 | let wrong_input = b"wrong input"; 344 | let server_evaluate = server.evaluate(wrong_input).unwrap(); 345 | assert!(client_finalize != server_evaluate); 346 | } 347 | 348 | fn zeroize_oprf_client() { 349 | let input = b"input"; 350 | let mut rng = OsRng; 351 | let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); 352 | 353 | let mut state = client_blind_result.state; 354 | unsafe { ptr::drop_in_place(&mut state) }; 355 | assert!(state.serialize().iter().all(|&x| x == 0)); 356 | 357 | let mut message = client_blind_result.message; 358 | unsafe { ptr::drop_in_place(&mut message) }; 359 | assert!(message.serialize().iter().all(|&x| x == 0)); 360 | } 361 | 362 | fn zeroize_oprf_server() { 363 | let input = b"input"; 364 | let mut rng = OsRng; 365 | let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); 366 | let server = OprfServer::::new(&mut rng).unwrap(); 367 | let mut message = server.blind_evaluate(&client_blind_result.message); 368 | 369 | let mut state = server; 370 | unsafe { ptr::drop_in_place(&mut state) }; 371 | assert!(state.serialize().iter().all(|&x| x == 0)); 372 | 373 | unsafe { ptr::drop_in_place(&mut message) }; 374 | assert!(message.serialize().iter().all(|&x| x == 0)); 375 | } 376 | 377 | #[test] 378 | fn test_functionality() -> Result<()> { 379 | use p256::NistP256; 380 | use p384::NistP384; 381 | use p521::NistP521; 382 | 383 | #[cfg(feature = "ristretto255")] 384 | { 385 | use crate::Ristretto255; 386 | 387 | base_retrieval::(); 388 | base_inversion_unsalted::(); 389 | server_evaluate::(); 390 | 391 | zeroize_oprf_client::(); 392 | zeroize_oprf_server::(); 393 | } 394 | 395 | base_retrieval::(); 396 | base_inversion_unsalted::(); 397 | server_evaluate::(); 398 | 399 | zeroize_oprf_client::(); 400 | zeroize_oprf_server::(); 401 | 402 | base_retrieval::(); 403 | base_inversion_unsalted::(); 404 | server_evaluate::(); 405 | 406 | zeroize_oprf_client::(); 407 | zeroize_oprf_server::(); 408 | 409 | base_retrieval::(); 410 | base_inversion_unsalted::(); 411 | server_evaluate::(); 412 | 413 | zeroize_oprf_client::(); 414 | zeroize_oprf_server::(); 415 | 416 | Ok(()) 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Common functionality between multiple OPRF modes. 10 | 11 | use core::convert::TryFrom; 12 | use core::ops::Add; 13 | 14 | use derive_where::derive_where; 15 | use digest::{Digest, Output, OutputSizeUser}; 16 | use generic_array::sequence::Concat; 17 | use generic_array::typenum::{IsLess, Unsigned, U2, U256, U9}; 18 | use generic_array::{ArrayLength, GenericArray}; 19 | use rand_core::{CryptoRng, RngCore}; 20 | use subtle::ConstantTimeEq; 21 | 22 | #[cfg(feature = "serde")] 23 | use crate::serialization::serde::{Element, Scalar}; 24 | use crate::{CipherSuite, Error, Group, InternalError, Result}; 25 | 26 | /////////////// 27 | // Constants // 28 | // ========= // 29 | /////////////// 30 | 31 | pub(crate) const STR_FINALIZE: [u8; 8] = *b"Finalize"; 32 | pub(crate) const STR_SEED: [u8; 5] = *b"Seed-"; 33 | pub(crate) const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair"; 34 | pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite"; 35 | pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge"; 36 | pub(crate) const STR_INFO: [u8; 4] = *b"Info"; 37 | pub(crate) const STR_OPRF: [u8; 7] = *b"OPRFV1-"; 38 | pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-"; 39 | pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-"; 40 | 41 | /// Determines the mode of operation (either base mode or verifiable mode). This 42 | /// is only used for custom implementations for [`Group`]. 43 | #[derive(Clone, Copy, Debug)] 44 | pub enum Mode { 45 | /// Non-verifiable mode. 46 | Oprf, 47 | /// Verifiable mode. 48 | Voprf, 49 | /// Partially-oblivious mode. 50 | Poprf, 51 | } 52 | 53 | impl Mode { 54 | /// Mode as it is represented in a context string. 55 | pub fn to_u8(self) -> u8 { 56 | match self { 57 | Mode::Oprf => 0, 58 | Mode::Voprf => 1, 59 | Mode::Poprf => 2, 60 | } 61 | } 62 | } 63 | 64 | //////////////////////////// 65 | // High-level API Structs // 66 | // ====================== // 67 | //////////////////////////// 68 | 69 | /// The first client message sent from a client (either verifiable or not) to a 70 | /// server (either verifiable or not). 71 | #[derive_where(Clone, ZeroizeOnDrop)] 72 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Elem)] 73 | #[cfg_attr( 74 | feature = "serde", 75 | derive(serde::Deserialize, serde::Serialize), 76 | serde(bound = "") 77 | )] 78 | pub struct BlindedElement( 79 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 80 | pub(crate) ::Elem, 81 | ); 82 | 83 | /// The server's response to the [BlindedElement] message from a client (either 84 | /// verifiable or not) to a server (either verifiable or not). 85 | #[derive_where(Clone, ZeroizeOnDrop)] 86 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Elem)] 87 | #[cfg_attr( 88 | feature = "serde", 89 | derive(serde::Deserialize, serde::Serialize), 90 | serde(bound = "") 91 | )] 92 | pub struct EvaluationElement( 93 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 94 | pub(crate) ::Elem, 95 | ); 96 | 97 | /// Contains prepared [`EvaluationElement`]s by a server batch evaluate 98 | /// preparation. 99 | #[derive_where(Clone, ZeroizeOnDrop)] 100 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Elem)] 101 | #[cfg_attr( 102 | feature = "serde", 103 | derive(serde::Deserialize, serde::Serialize), 104 | serde(bound = "") 105 | )] 106 | pub struct PreparedEvaluationElement(pub(crate) EvaluationElement); 107 | 108 | /// A proof produced by a server that the OPRF output matches against a server 109 | /// public key. 110 | #[derive_where(Clone, ZeroizeOnDrop)] 111 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar)] 112 | #[cfg_attr( 113 | feature = "serde", 114 | derive(serde::Deserialize, serde::Serialize), 115 | serde(bound = "") 116 | )] 117 | pub struct Proof { 118 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 119 | pub(crate) c_scalar: ::Scalar, 120 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 121 | pub(crate) s_scalar: ::Scalar, 122 | } 123 | 124 | ///////////////////// 125 | // Proof Functions // 126 | // =============== // 127 | ///////////////////// 128 | 129 | /// Can only fail with [`Error::Batch`]. 130 | #[allow(clippy::many_single_char_names)] 131 | pub(crate) fn generate_proof( 132 | rng: &mut R, 133 | k: ::Scalar, 134 | a: ::Elem, 135 | b: ::Elem, 136 | cs: impl ExactSizeIterator::Elem>, 137 | ds: impl ExactSizeIterator::Elem>, 138 | mode: Mode, 139 | ) -> Result> { 140 | // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1 141 | 142 | let (m, z) = compute_composites::(Some(k), b, cs, ds, mode)?; 143 | 144 | let r = CS::Group::random_scalar(rng); 145 | let t2 = a * &r; 146 | let t3 = m * &r; 147 | 148 | // Bm = GG.SerializeElement(B) 149 | let bm = CS::Group::serialize_elem(b); 150 | // a0 = GG.SerializeElement(M) 151 | let a0 = CS::Group::serialize_elem(m); 152 | // a1 = GG.SerializeElement(Z) 153 | let a1 = CS::Group::serialize_elem(z); 154 | // a2 = GG.SerializeElement(t2) 155 | let a2 = CS::Group::serialize_elem(t2); 156 | // a3 = GG.SerializeElement(t3) 157 | let a3 = CS::Group::serialize_elem(t3); 158 | 159 | let elem_len = ::ElemLen::U16.to_be_bytes(); 160 | 161 | // h2Input = I2OSP(len(Bm), 2) || Bm || 162 | // I2OSP(len(a0), 2) || a0 || 163 | // I2OSP(len(a1), 2) || a1 || 164 | // I2OSP(len(a2), 2) || a2 || 165 | // I2OSP(len(a3), 2) || a3 || 166 | // "Challenge" 167 | let h2_input = [ 168 | &elem_len, 169 | bm.as_slice(), 170 | &elem_len, 171 | &a0, 172 | &elem_len, 173 | &a1, 174 | &elem_len, 175 | &a2, 176 | &elem_len, 177 | &a3, 178 | &STR_CHALLENGE, 179 | ]; 180 | 181 | let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); 182 | // This can't fail, the size of the `input` is known. 183 | let c_scalar = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); 184 | let s_scalar = r - &(c_scalar * &k); 185 | 186 | Ok(Proof { c_scalar, s_scalar }) 187 | } 188 | 189 | /// Can only fail with [`Error::ProofVerification`] or [`Error::Batch`]. 190 | #[allow(clippy::many_single_char_names)] 191 | pub(crate) fn verify_proof( 192 | a: ::Elem, 193 | b: ::Elem, 194 | cs: impl ExactSizeIterator::Elem>, 195 | ds: impl ExactSizeIterator::Elem>, 196 | proof: &Proof, 197 | mode: Mode, 198 | ) -> Result<()> { 199 | // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.2 200 | let (m, z) = compute_composites::(None, b, cs, ds, mode)?; 201 | let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar); 202 | let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar); 203 | 204 | // Bm = GG.SerializeElement(B) 205 | let bm = CS::Group::serialize_elem(b); 206 | // a0 = GG.SerializeElement(M) 207 | let a0 = CS::Group::serialize_elem(m); 208 | // a1 = GG.SerializeElement(Z) 209 | let a1 = CS::Group::serialize_elem(z); 210 | // a2 = GG.SerializeElement(t2) 211 | let a2 = CS::Group::serialize_elem(t2); 212 | // a3 = GG.SerializeElement(t3) 213 | let a3 = CS::Group::serialize_elem(t3); 214 | 215 | let elem_len = ::ElemLen::U16.to_be_bytes(); 216 | 217 | // h2Input = I2OSP(len(Bm), 2) || Bm || 218 | // I2OSP(len(a0), 2) || a0 || 219 | // I2OSP(len(a1), 2) || a1 || 220 | // I2OSP(len(a2), 2) || a2 || 221 | // I2OSP(len(a3), 2) || a3 || 222 | // "Challenge" 223 | let h2_input = [ 224 | &elem_len, 225 | bm.as_slice(), 226 | &elem_len, 227 | &a0, 228 | &elem_len, 229 | &a1, 230 | &elem_len, 231 | &a2, 232 | &elem_len, 233 | &a3, 234 | &STR_CHALLENGE, 235 | ]; 236 | 237 | let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); 238 | // This can't fail, the size of the `input` is known. 239 | let c = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); 240 | 241 | match c.ct_eq(&proof.c_scalar).into() { 242 | true => Ok(()), 243 | false => Err(Error::ProofVerification), 244 | } 245 | } 246 | 247 | type ComputeCompositesResult = ( 248 | <::Group as Group>::Elem, 249 | <::Group as Group>::Elem, 250 | ); 251 | 252 | /// Can only fail with [`Error::Batch`]. 253 | fn compute_composites< 254 | CS: CipherSuite, 255 | IC: Iterator::Elem> + ExactSizeIterator, 256 | ID: Iterator::Elem> + ExactSizeIterator, 257 | >( 258 | k_option: Option<::Scalar>, 259 | b: ::Elem, 260 | c_slice: IC, 261 | d_slice: ID, 262 | mode: Mode, 263 | ) -> Result> { 264 | // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1 265 | 266 | let elem_len = ::ElemLen::U16.to_be_bytes(); 267 | 268 | if c_slice.len() != d_slice.len() { 269 | return Err(Error::Batch); 270 | } 271 | 272 | let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?; 273 | 274 | // seedDST = "Seed-" || contextString 275 | let seed_dst = Dst::new::(STR_SEED, mode); 276 | 277 | // h1Input = I2OSP(len(Bm), 2) || Bm || 278 | // I2OSP(len(seedDST), 2) || seedDST 279 | // seed = Hash(h1Input) 280 | let seed = CS::Hash::new() 281 | .chain_update(elem_len) 282 | .chain_update(CS::Group::serialize_elem(b)) 283 | .chain_update(seed_dst.i2osp_2()) 284 | .chain_update_multi(&seed_dst.as_dst()) 285 | .finalize(); 286 | let seed_len = i2osp_2_array::<::OutputSize>(); 287 | 288 | let mut m = CS::Group::identity_elem(); 289 | let mut z = CS::Group::identity_elem(); 290 | 291 | for (i, (c, d)) in (0..len).zip(c_slice.zip(d_slice)) { 292 | // Ci = GG.SerializeElement(Cs[i]) 293 | let ci = CS::Group::serialize_elem(c); 294 | // Di = GG.SerializeElement(Ds[i]) 295 | let di = CS::Group::serialize_elem(d); 296 | // h2Input = I2OSP(len(seed), 2) || seed || I2OSP(i, 2) || 297 | // I2OSP(len(Ci), 2) || Ci || 298 | // I2OSP(len(Di), 2) || Di || 299 | // "Composite" 300 | let h2_input = [ 301 | seed_len.as_slice(), 302 | &seed, 303 | &i.to_be_bytes(), 304 | &elem_len, 305 | &ci, 306 | &elem_len, 307 | &di, 308 | &STR_COMPOSITE, 309 | ]; 310 | 311 | let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); 312 | // This can't fail, the size of the `input` is known. 313 | let di = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); 314 | m = c * &di + &m; 315 | z = match k_option { 316 | Some(_) => z, 317 | None => d * &di + &z, 318 | }; 319 | } 320 | 321 | z = match k_option { 322 | Some(k) => m * &k, 323 | None => z, 324 | }; 325 | 326 | Ok((m, z)) 327 | } 328 | 329 | ///////////////////// 330 | // Inner Functions // 331 | // =============== // 332 | ///////////////////// 333 | 334 | /// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`]. 335 | pub(crate) fn derive_key_internal( 336 | seed: &[u8], 337 | info: &[u8], 338 | mode: Mode, 339 | ) -> Result<::Scalar, Error> { 340 | let dst = Dst::new::(STR_DERIVE_KEYPAIR, mode); 341 | 342 | let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; 343 | 344 | for counter in 0_u8..=u8::MAX { 345 | // deriveInput = seed || I2OSP(len(info), 2) || info 346 | // skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair" 347 | // || contextString) 348 | let sk_s = CS::Group::hash_to_scalar::( 349 | &[seed, &info_len, info, &counter.to_be_bytes()], 350 | &dst.as_dst(), 351 | ) 352 | .map_err(|_| Error::DeriveKeyPair)?; 353 | 354 | if !bool::from(CS::Group::is_zero_scalar(sk_s)) { 355 | return Ok(sk_s); 356 | } 357 | } 358 | 359 | Err(Error::Protocol) 360 | } 361 | 362 | /// Corresponds to DeriveKeyPair() function from the VOPRF specification. 363 | /// 364 | /// # Errors 365 | /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer 366 | /// then `u16::MAX - 3`. 367 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 368 | #[cfg(feature = "danger")] 369 | pub fn derive_key( 370 | seed: &[u8], 371 | info: &[u8], 372 | mode: Mode, 373 | ) -> Result<::Scalar, Error> { 374 | derive_key_internal::(seed, info, mode) 375 | } 376 | 377 | type DeriveKeypairResult = ( 378 | <::Group as Group>::Scalar, 379 | <::Group as Group>::Elem, 380 | ); 381 | 382 | /// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`]. 383 | pub(crate) fn derive_keypair( 384 | seed: &[u8], 385 | info: &[u8], 386 | mode: Mode, 387 | ) -> Result, Error> { 388 | let sk_s = derive_key_internal::(seed, info, mode)?; 389 | let pk_s = CS::Group::base_elem() * &sk_s; 390 | 391 | Ok((sk_s, pk_s)) 392 | } 393 | 394 | /// Inner function for blind that assumes that the blinding factor has already 395 | /// been chosen, and therefore takes it as input. Does not check if the blinding 396 | /// factor is non-zero. 397 | /// 398 | /// Can only fail with [`Error::Input`]. 399 | pub(crate) fn deterministic_blind_unchecked( 400 | input: &[u8], 401 | blind: &::Scalar, 402 | mode: Mode, 403 | ) -> Result<::Elem> { 404 | let hashed_point = hash_to_group::(input, mode)?; 405 | Ok(hashed_point * blind) 406 | } 407 | 408 | /// Hashes `input` to a point on the curve 409 | pub(crate) fn hash_to_group( 410 | input: &[u8], 411 | mode: Mode, 412 | ) -> Result<::Elem> { 413 | let dst = Dst::new::(STR_HASH_TO_GROUP, mode); 414 | CS::Group::hash_to_curve::(&[input], &dst.as_dst()).map_err(|_| Error::Input) 415 | } 416 | 417 | /// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF. 418 | /// Returned values can only fail with [`Error::Input`]. 419 | pub(crate) fn server_evaluate_hash_input( 420 | input: &[u8], 421 | info: Option<&[u8]>, 422 | issued_element: GenericArray::Group as Group>::ElemLen>, 423 | ) -> Result> { 424 | // OPRF & VOPRF 425 | // hashInput = I2OSP(len(input), 2) || input || 426 | // I2OSP(len(issuedElement), 2) || issuedElement || 427 | // "Finalize" 428 | // return Hash(hashInput) 429 | // 430 | // POPRF 431 | // hashInput = I2OSP(len(input), 2) || input || 432 | // I2OSP(len(info), 2) || info || 433 | // I2OSP(len(issuedElement), 2) || issuedElement || 434 | // "Finalize" 435 | 436 | let mut hash = CS::Hash::new() 437 | .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?) 438 | .chain_update(input.as_ref()); 439 | if let Some(info) = info { 440 | hash = hash 441 | .chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Input)?) 442 | .chain_update(info.as_ref()); 443 | } 444 | Ok(hash 445 | .chain_update(i2osp_2(issued_element.as_slice().len()).map_err(|_| Error::Input)?) 446 | .chain_update(issued_element) 447 | .chain_update(STR_FINALIZE) 448 | .finalize()) 449 | } 450 | 451 | pub(crate) struct Dst { 452 | dst_1: GenericArray, 453 | dst_2: &'static str, 454 | } 455 | 456 | impl Dst { 457 | pub(crate) fn new(par_1: T, mode: Mode) -> Self 458 | where 459 | CS: CipherSuite, 460 | T: Into>, 461 | TL: ArrayLength + Add, 462 | { 463 | let par_1 = par_1.into(); 464 | // Generates the contextString parameter as defined in 465 | // 466 | let par_2 = GenericArray::from(STR_OPRF) 467 | .concat([mode.to_u8()].into()) 468 | .concat([b'-'].into()); 469 | 470 | let dst_1 = par_1.concat(par_2); 471 | let dst_2 = CS::ID; 472 | 473 | assert!( 474 | L::USIZE + dst_2.len() <= u16::MAX.into(), 475 | "constructed DST longer then {}", 476 | u16::MAX 477 | ); 478 | 479 | Self { dst_1, dst_2 } 480 | } 481 | 482 | pub(crate) fn as_dst(&self) -> [&[u8]; 2] { 483 | [&self.dst_1, self.dst_2.as_bytes()] 484 | } 485 | 486 | pub(crate) fn i2osp_2(&self) -> [u8; 2] { 487 | u16::try_from(L::USIZE + self.dst_2.len()) 488 | .unwrap() 489 | .to_be_bytes() 490 | } 491 | } 492 | 493 | trait DigestExt { 494 | fn chain_update_multi(self, data: &[&[u8]]) -> Self; 495 | } 496 | 497 | impl DigestExt for T 498 | where 499 | T: Digest, 500 | { 501 | fn chain_update_multi(mut self, datas: &[&[u8]]) -> Self { 502 | for data in datas { 503 | self.update(data) 504 | } 505 | 506 | self 507 | } 508 | } 509 | 510 | /////////////////////// 511 | // Utility Functions // 512 | // ================= // 513 | /////////////////////// 514 | 515 | pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> { 516 | u16::try_from(input) 517 | .map(|input| input.to_be_bytes()) 518 | .map_err(|_| InternalError::I2osp) 519 | } 520 | 521 | pub(crate) fn i2osp_2_array>() -> GenericArray { 522 | L::U16.to_be_bytes().into() 523 | } 524 | -------------------------------------------------------------------------------- /src/tests/test_cfrg_vectors.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | use alloc::string::String; 10 | use alloc::vec; 11 | use alloc::vec::Vec; 12 | 13 | use serde_json::Value; 14 | 15 | use crate::tests::mock_rng::CycleRng; 16 | use crate::tests::parser::*; 17 | use crate::{ 18 | BlindedElement, CipherSuite, EvaluationElement, Group, OprfClient, OprfServer, PoprfClient, 19 | PoprfServer, PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluatePrepareResult, 20 | Proof, Result, VoprfClient, VoprfServer, VoprfServerBatchEvaluateFinishResult, 21 | }; 22 | 23 | #[derive(Debug)] 24 | struct VOPRFTestVectorParameters { 25 | seed: Vec, 26 | sksm: Vec, 27 | pksm: Vec, 28 | input: Vec>, 29 | info: Vec, 30 | key_info: Vec, 31 | blind: Vec>, 32 | blinded_element: Vec>, 33 | evaluation_element: Vec>, 34 | proof: Vec, 35 | proof_random_scalar: Vec, 36 | output: Vec>, 37 | } 38 | 39 | fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters { 40 | VOPRFTestVectorParameters { 41 | seed: decode(values, "Seed"), 42 | sksm: decode(values, "skSm"), 43 | pksm: decode(values, "pkSm"), 44 | input: decode_vec(values, "Input"), 45 | info: decode(values, "Info"), 46 | key_info: decode(values, "KeyInfo"), 47 | blind: decode_vec(values, "Blind"), 48 | blinded_element: decode_vec(values, "BlindedElement"), 49 | evaluation_element: decode_vec(values, "EvaluationElement"), 50 | proof: decode(values, "Proof"), 51 | proof_random_scalar: decode(values, "ProofRandomScalar"), 52 | output: decode_vec(values, "Output"), 53 | } 54 | } 55 | 56 | fn decode(values: &Value, key: &str) -> Vec { 57 | values[key] 58 | .as_str() 59 | .and_then(|s| hex::decode(s).ok()) 60 | .unwrap_or_default() 61 | } 62 | 63 | fn decode_vec(values: &Value, key: &str) -> Vec> { 64 | let s = values[key].as_str().unwrap(); 65 | let res = match s.contains(',') { 66 | true => Some(s.split(',').map(|x| hex::decode(x).unwrap()).collect()), 67 | false => Some(vec![hex::decode(s).unwrap()]), 68 | }; 69 | res.unwrap() 70 | } 71 | 72 | macro_rules! json_to_test_vectors { 73 | ( $v:ident, $cs:expr, $mode:expr ) => { 74 | $v[$cs][$mode] 75 | .as_array() 76 | .into_iter() 77 | .flatten() 78 | .map(populate_test_vectors) 79 | .collect::>() 80 | }; 81 | } 82 | 83 | #[test] 84 | fn test_vectors() -> Result<()> { 85 | use p256::NistP256; 86 | use p384::NistP384; 87 | use p521::NistP521; 88 | 89 | let rfc: Value = serde_json::from_str(rfc_to_json(super::cfrg_vectors::VECTORS).as_str()) 90 | .expect("Could not parse json"); 91 | 92 | #[cfg(feature = "ristretto255")] 93 | { 94 | use crate::Ristretto255; 95 | 96 | let ristretto_oprf_tvs = json_to_test_vectors!( 97 | rfc, 98 | String::from("ristretto255-SHA512"), 99 | String::from("OPRF") 100 | ); 101 | assert_ne!(ristretto_oprf_tvs.len(), 0); 102 | test_oprf_seed_to_key::(&ristretto_oprf_tvs)?; 103 | test_oprf_blind::(&ristretto_oprf_tvs)?; 104 | test_oprf_blind_evaluate::(&ristretto_oprf_tvs)?; 105 | test_oprf_finalize::(&ristretto_oprf_tvs)?; 106 | test_oprf_evaluate::(&ristretto_oprf_tvs)?; 107 | 108 | let ristretto_voprf_tvs = json_to_test_vectors!( 109 | rfc, 110 | String::from("ristretto255-SHA512"), 111 | String::from("VOPRF") 112 | ); 113 | assert_ne!(ristretto_voprf_tvs.len(), 0); 114 | test_voprf_seed_to_key::(&ristretto_voprf_tvs)?; 115 | test_voprf_blind::(&ristretto_voprf_tvs)?; 116 | test_voprf_blind_evaluate::(&ristretto_voprf_tvs)?; 117 | test_voprf_finalize::(&ristretto_voprf_tvs)?; 118 | test_voprf_evaluate::(&ristretto_voprf_tvs)?; 119 | 120 | let ristretto_poprf_tvs = json_to_test_vectors!( 121 | rfc, 122 | String::from("ristretto255-SHA512"), 123 | String::from("POPRF") 124 | ); 125 | assert_ne!(ristretto_poprf_tvs.len(), 0); 126 | test_poprf_seed_to_key::(&ristretto_poprf_tvs)?; 127 | test_poprf_blind::(&ristretto_poprf_tvs)?; 128 | test_poprf_blind_evaluate::(&ristretto_poprf_tvs)?; 129 | test_poprf_finalize::(&ristretto_poprf_tvs)?; 130 | test_poprf_evaluate::(&ristretto_poprf_tvs)?; 131 | } 132 | 133 | let p256_oprf_tvs = 134 | json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("OPRF")); 135 | assert_ne!(p256_oprf_tvs.len(), 0); 136 | test_oprf_seed_to_key::(&p256_oprf_tvs)?; 137 | test_oprf_blind::(&p256_oprf_tvs)?; 138 | test_oprf_blind_evaluate::(&p256_oprf_tvs)?; 139 | test_oprf_finalize::(&p256_oprf_tvs)?; 140 | test_oprf_evaluate::(&p256_oprf_tvs)?; 141 | 142 | let p256_voprf_tvs = 143 | json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("VOPRF")); 144 | assert_ne!(p256_voprf_tvs.len(), 0); 145 | test_voprf_seed_to_key::(&p256_voprf_tvs)?; 146 | test_voprf_blind::(&p256_voprf_tvs)?; 147 | test_voprf_blind_evaluate::(&p256_voprf_tvs)?; 148 | test_voprf_finalize::(&p256_voprf_tvs)?; 149 | test_voprf_evaluate::(&p256_voprf_tvs)?; 150 | 151 | let p256_poprf_tvs = 152 | json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("POPRF")); 153 | assert_ne!(p256_poprf_tvs.len(), 0); 154 | test_poprf_seed_to_key::(&p256_poprf_tvs)?; 155 | test_poprf_blind::(&p256_poprf_tvs)?; 156 | test_poprf_blind_evaluate::(&p256_poprf_tvs)?; 157 | test_poprf_finalize::(&p256_poprf_tvs)?; 158 | test_poprf_evaluate::(&p256_poprf_tvs)?; 159 | 160 | let p384_oprf_tvs = 161 | json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("OPRF")); 162 | assert_ne!(p384_oprf_tvs.len(), 0); 163 | test_oprf_seed_to_key::(&p384_oprf_tvs)?; 164 | test_oprf_blind::(&p384_oprf_tvs)?; 165 | test_oprf_blind_evaluate::(&p384_oprf_tvs)?; 166 | test_oprf_finalize::(&p384_oprf_tvs)?; 167 | test_oprf_evaluate::(&p384_oprf_tvs)?; 168 | 169 | let p384_voprf_tvs = 170 | json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("VOPRF")); 171 | assert_ne!(p384_voprf_tvs.len(), 0); 172 | test_voprf_seed_to_key::(&p384_voprf_tvs)?; 173 | test_voprf_blind::(&p384_voprf_tvs)?; 174 | test_voprf_blind_evaluate::(&p384_voprf_tvs)?; 175 | test_voprf_finalize::(&p384_voprf_tvs)?; 176 | test_voprf_evaluate::(&p384_voprf_tvs)?; 177 | 178 | let p384_poprf_tvs = 179 | json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("POPRF")); 180 | assert_ne!(p384_poprf_tvs.len(), 0); 181 | test_poprf_seed_to_key::(&p384_poprf_tvs)?; 182 | test_poprf_blind::(&p384_poprf_tvs)?; 183 | test_poprf_blind_evaluate::(&p384_poprf_tvs)?; 184 | test_poprf_finalize::(&p384_poprf_tvs)?; 185 | test_poprf_evaluate::(&p384_poprf_tvs)?; 186 | 187 | let p521_oprf_tvs = 188 | json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("OPRF")); 189 | assert_ne!(p521_oprf_tvs.len(), 0); 190 | test_oprf_seed_to_key::(&p521_oprf_tvs)?; 191 | test_oprf_blind::(&p521_oprf_tvs)?; 192 | test_oprf_blind_evaluate::(&p521_oprf_tvs)?; 193 | test_oprf_finalize::(&p521_oprf_tvs)?; 194 | test_oprf_evaluate::(&p521_oprf_tvs)?; 195 | 196 | let p521_voprf_tvs = 197 | json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("VOPRF")); 198 | assert_ne!(p521_voprf_tvs.len(), 0); 199 | test_voprf_seed_to_key::(&p521_voprf_tvs)?; 200 | test_voprf_blind::(&p521_voprf_tvs)?; 201 | test_voprf_blind_evaluate::(&p521_voprf_tvs)?; 202 | test_voprf_finalize::(&p521_voprf_tvs)?; 203 | test_voprf_evaluate::(&p521_voprf_tvs)?; 204 | 205 | let p521_poprf_tvs = 206 | json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("POPRF")); 207 | assert_ne!(p521_poprf_tvs.len(), 0); 208 | test_poprf_seed_to_key::(&p521_poprf_tvs)?; 209 | test_poprf_blind::(&p521_poprf_tvs)?; 210 | test_poprf_blind_evaluate::(&p521_poprf_tvs)?; 211 | test_poprf_finalize::(&p521_poprf_tvs)?; 212 | test_poprf_evaluate::(&p521_poprf_tvs)?; 213 | 214 | Ok(()) 215 | } 216 | 217 | fn test_oprf_seed_to_key(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 218 | for parameters in tvs { 219 | let server = OprfServer::::new_from_seed(¶meters.seed, ¶meters.key_info)?; 220 | 221 | assert_eq!( 222 | ¶meters.sksm, 223 | &CS::Group::serialize_scalar(server.get_private_key()).to_vec() 224 | ); 225 | } 226 | Ok(()) 227 | } 228 | 229 | fn test_voprf_seed_to_key(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 230 | for parameters in tvs { 231 | let server = VoprfServer::::new_from_seed(¶meters.seed, ¶meters.key_info)?; 232 | 233 | assert_eq!( 234 | ¶meters.sksm, 235 | &CS::Group::serialize_scalar(server.get_private_key()).to_vec() 236 | ); 237 | assert_eq!( 238 | ¶meters.pksm, 239 | CS::Group::serialize_elem(server.get_public_key()).as_slice() 240 | ); 241 | } 242 | Ok(()) 243 | } 244 | 245 | fn test_poprf_seed_to_key(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 246 | for parameters in tvs { 247 | let server = PoprfServer::::new_from_seed(¶meters.seed, ¶meters.key_info)?; 248 | 249 | assert_eq!( 250 | ¶meters.sksm, 251 | &CS::Group::serialize_scalar(server.get_private_key()).to_vec() 252 | ); 253 | assert_eq!( 254 | ¶meters.pksm, 255 | CS::Group::serialize_elem(server.get_public_key()).as_slice() 256 | ); 257 | } 258 | Ok(()) 259 | } 260 | 261 | // Tests input -> blind, blinded_element 262 | fn test_oprf_blind(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 263 | for parameters in tvs { 264 | for i in 0..parameters.input.len() { 265 | let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?; 266 | let client_result = 267 | OprfClient::::deterministic_blind_unchecked(¶meters.input[i], blind)?; 268 | 269 | assert_eq!( 270 | ¶meters.blind[i], 271 | &CS::Group::serialize_scalar(client_result.state.blind).to_vec() 272 | ); 273 | assert_eq!( 274 | parameters.blinded_element[i].as_slice(), 275 | client_result.message.serialize().as_slice(), 276 | ); 277 | } 278 | } 279 | Ok(()) 280 | } 281 | 282 | // Tests input -> blind, blinded_element 283 | fn test_voprf_blind(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 284 | for parameters in tvs { 285 | for i in 0..parameters.input.len() { 286 | let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?; 287 | let client_blind_result = 288 | VoprfClient::::deterministic_blind_unchecked(¶meters.input[i], blind)?; 289 | 290 | assert_eq!( 291 | ¶meters.blind[i], 292 | &CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec() 293 | ); 294 | assert_eq!( 295 | parameters.blinded_element[i].as_slice(), 296 | client_blind_result.message.serialize().as_slice(), 297 | ); 298 | } 299 | } 300 | Ok(()) 301 | } 302 | 303 | // Tests input -> blind, blinded_element 304 | fn test_poprf_blind(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 305 | for parameters in tvs { 306 | for i in 0..parameters.input.len() { 307 | let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?; 308 | let client_blind_result = 309 | PoprfClient::::deterministic_blind_unchecked(¶meters.input[i], blind)?; 310 | 311 | assert_eq!( 312 | ¶meters.blind[i], 313 | &CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec() 314 | ); 315 | assert_eq!( 316 | parameters.blinded_element[i].as_slice(), 317 | client_blind_result.message.serialize().as_slice(), 318 | ); 319 | } 320 | } 321 | Ok(()) 322 | } 323 | 324 | // Tests sksm, blinded_element -> evaluation_element 325 | fn test_oprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 326 | for parameters in tvs { 327 | for i in 0..parameters.input.len() { 328 | let server = OprfServer::::new_with_key(¶meters.sksm)?; 329 | let message = server.blind_evaluate(&BlindedElement::deserialize( 330 | ¶meters.blinded_element[i], 331 | )?); 332 | 333 | assert_eq!( 334 | ¶meters.evaluation_element[i], 335 | &message.serialize().as_slice() 336 | ); 337 | } 338 | } 339 | Ok(()) 340 | } 341 | 342 | fn test_voprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 343 | for parameters in tvs { 344 | let mut rng = CycleRng::new(parameters.proof_random_scalar.clone()); 345 | let server = VoprfServer::::new_with_key(¶meters.sksm)?; 346 | 347 | let mut blinded_elements = vec![]; 348 | for blinded_element_bytes in ¶meters.blinded_element { 349 | blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?); 350 | } 351 | 352 | let prepared_evaluation_elements = 353 | server.batch_blind_evaluate_prepare(blinded_elements.iter()); 354 | let prepared_elements: Vec<_> = prepared_evaluation_elements.collect(); 355 | let VoprfServerBatchEvaluateFinishResult { messages, proof } = server 356 | .batch_blind_evaluate_finish(&mut rng, blinded_elements.iter(), &prepared_elements)?; 357 | let messages: Vec<_> = messages.collect(); 358 | 359 | for (parameter, message) in parameters.evaluation_element.iter().zip(messages) { 360 | assert_eq!(¶meter, &message.serialize().as_slice()); 361 | } 362 | 363 | assert_eq!(¶meters.proof, &proof.serialize().as_slice()); 364 | } 365 | Ok(()) 366 | } 367 | 368 | fn test_poprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 369 | for parameters in tvs { 370 | let mut rng = CycleRng::new(parameters.proof_random_scalar.clone()); 371 | let server = PoprfServer::::new_with_key(¶meters.sksm)?; 372 | 373 | let mut blinded_elements = vec![]; 374 | for blinded_element_bytes in ¶meters.blinded_element { 375 | blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?); 376 | } 377 | 378 | let PoprfServerBatchEvaluatePrepareResult { 379 | prepared_evaluation_elements, 380 | prepared_tweak, 381 | } = server.batch_blind_evaluate_prepare(blinded_elements.iter(), Some(¶meters.info))?; 382 | let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect(); 383 | let PoprfServerBatchEvaluateFinishResult { messages, proof } = 384 | PoprfServer::batch_blind_evaluate_finish::<_, _, Vec<_>>( 385 | &mut rng, 386 | blinded_elements.iter(), 387 | &prepared_evaluation_elements, 388 | &prepared_tweak, 389 | ) 390 | .unwrap(); 391 | 392 | let messages: Vec<_> = messages.collect(); 393 | 394 | for (parameter, message) in parameters.evaluation_element.iter().zip(messages) { 395 | assert_eq!(¶meter, &message.serialize().as_slice()); 396 | } 397 | 398 | assert_eq!(¶meters.proof, &proof.serialize().as_slice()); 399 | } 400 | Ok(()) 401 | } 402 | 403 | // Tests input, blind, evaluation_element -> output 404 | fn test_oprf_finalize(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 405 | for parameters in tvs { 406 | for i in 0..parameters.input.len() { 407 | let client = 408 | OprfClient::::from_blind(CS::Group::deserialize_scalar(¶meters.blind[i])?); 409 | 410 | let client_finalize_result = client.finalize( 411 | ¶meters.input[i], 412 | &EvaluationElement::deserialize(¶meters.evaluation_element[i])?, 413 | )?; 414 | 415 | assert_eq!(¶meters.output[i], &client_finalize_result.to_vec()); 416 | } 417 | } 418 | Ok(()) 419 | } 420 | 421 | fn test_voprf_finalize(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 422 | for parameters in tvs { 423 | let mut clients = vec![]; 424 | for i in 0..parameters.input.len() { 425 | let client = VoprfClient::::from_blind_and_element( 426 | CS::Group::deserialize_scalar(¶meters.blind[i])?, 427 | CS::Group::deserialize_elem(¶meters.blinded_element[i])?, 428 | ); 429 | clients.push(client.clone()); 430 | } 431 | 432 | let messages: Vec<_> = parameters 433 | .evaluation_element 434 | .iter() 435 | .map(|x| EvaluationElement::deserialize(x).unwrap()) 436 | .collect(); 437 | 438 | let batch_result = VoprfClient::batch_finalize( 439 | ¶meters.input, 440 | &clients, 441 | &messages, 442 | &Proof::deserialize(¶meters.proof)?, 443 | CS::Group::deserialize_elem(¶meters.pksm)?, 444 | )?; 445 | 446 | assert_eq!( 447 | parameters.output, 448 | batch_result 449 | .map(|arr| arr.map(|message| message.to_vec())) 450 | .collect::>>()? 451 | ); 452 | } 453 | Ok(()) 454 | } 455 | 456 | fn test_poprf_finalize(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 457 | for parameters in tvs { 458 | let mut clients = vec![]; 459 | for i in 0..parameters.input.len() { 460 | let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?; 461 | let client_blind_result = 462 | PoprfClient::::deterministic_blind_unchecked(¶meters.input[i], blind)?; 463 | let client = client_blind_result.state; 464 | clients.push(client.clone()); 465 | } 466 | 467 | let messages: Vec<_> = parameters 468 | .evaluation_element 469 | .iter() 470 | .map(|x| EvaluationElement::deserialize(x).unwrap()) 471 | .collect(); 472 | 473 | let batch_result = PoprfClient::batch_finalize( 474 | parameters.input.iter().map(|input| input.as_slice()), 475 | &clients, 476 | &messages, 477 | &Proof::deserialize(¶meters.proof)?, 478 | CS::Group::deserialize_elem(¶meters.pksm)?, 479 | Some(¶meters.info), 480 | )?; 481 | 482 | let result: Vec> = batch_result.map(|arr| arr.unwrap().to_vec()).collect(); 483 | 484 | assert_eq!(parameters.output, result); 485 | } 486 | Ok(()) 487 | } 488 | 489 | // Tests input, sksm -> output 490 | fn test_oprf_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 491 | for parameters in tvs { 492 | for i in 0..parameters.input.len() { 493 | let server = OprfServer::::new_with_key(¶meters.sksm)?; 494 | 495 | let server_evaluate_result = server.evaluate(¶meters.input[i])?; 496 | 497 | assert_eq!(¶meters.output[i], &server_evaluate_result.to_vec()); 498 | } 499 | } 500 | Ok(()) 501 | } 502 | 503 | fn test_voprf_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 504 | for parameters in tvs { 505 | for i in 0..parameters.input.len() { 506 | let server = VoprfServer::::new_with_key(¶meters.sksm)?; 507 | 508 | let server_evaluate_result = server.evaluate(¶meters.input[i])?; 509 | 510 | assert_eq!(¶meters.output[i], &server_evaluate_result.to_vec()); 511 | } 512 | } 513 | Ok(()) 514 | } 515 | 516 | fn test_poprf_evaluate(tvs: &[VOPRFTestVectorParameters]) -> Result<()> { 517 | for parameters in tvs { 518 | for i in 0..parameters.input.len() { 519 | let server = PoprfServer::::new_with_key(¶meters.sksm)?; 520 | 521 | let server_evaluate_result = 522 | server.evaluate(¶meters.input[i], Some(¶meters.info))?; 523 | 524 | assert_eq!(¶meters.output[i], &server_evaluate_result.to_vec()); 525 | } 526 | } 527 | Ok(()) 528 | } 529 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! An implementation of a verifiable oblivious pseudorandom function (VOPRF) 10 | //! 11 | //! Note: This implementation is in sync with 12 | //! [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497). 13 | //! 14 | //! # Overview 15 | //! 16 | //! A verifiable oblivious pseudorandom function is a protocol that is evaluated 17 | //! between a client and a server. They must first agree on a finite cyclic 18 | //! group along with a point representation. 19 | //! 20 | //! We will use the following choice in this example: 21 | //! 22 | //! ```ignore 23 | //! type CipherSuite = voprf::Ristretto255; 24 | //! ``` 25 | //! 26 | //! ## Modes of Operation 27 | //! 28 | //! VOPRF can be used in three modes: 29 | //! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation 30 | //! with no support for the verification of the OPRF outputs 31 | //! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF 32 | //! evaluation where the outputs can be verified against a server public key 33 | //! (VOPRF) 34 | //! - [Partially Oblivious Verifiable Mode](#metadata), which corresponds to a 35 | //! VOPRF, where a public input can be supplied to the PRF computation 36 | //! 37 | //! In all of these modes, the protocol begins with a client blinding, followed 38 | //! by a server evaluation, and finishes with a client finalization and server 39 | //! evaluation. 40 | //! 41 | //! ## Base Mode 42 | //! 43 | //! In base mode, an [OprfClient] interacts with an [OprfServer] to compute the 44 | //! output of the OPRF. 45 | //! 46 | //! ### Server Setup 47 | //! 48 | //! The protocol begins with a setup phase, in which the server must run 49 | //! [OprfServer::new()] to produce an instance of itself. This instance must be 50 | //! persisted on the server and used for online client evaluations. 51 | //! 52 | //! ``` 53 | //! # #[cfg(feature = "ristretto255")] 54 | //! # type CipherSuite = voprf::Ristretto255; 55 | //! # #[cfg(not(feature = "ristretto255"))] 56 | //! # type CipherSuite = p256::NistP256; 57 | //! use rand::rngs::OsRng; 58 | //! use rand::RngCore; 59 | //! use voprf::OprfServer; 60 | //! 61 | //! let mut server_rng = OsRng; 62 | //! let server = OprfServer::::new(&mut server_rng); 63 | //! ``` 64 | //! 65 | //! ### Client Blinding 66 | //! 67 | //! In the first step, the client chooses an input, and runs [OprfClient::blind] 68 | //! to produce an [OprfClientBlindResult], which consists of a [BlindedElement] 69 | //! to be sent to the server and an [OprfClient] which must be persisted on the 70 | //! client for the final step of the VOPRF protocol. 71 | //! 72 | //! ``` 73 | //! # #[cfg(feature = "ristretto255")] 74 | //! # type CipherSuite = voprf::Ristretto255; 75 | //! # #[cfg(not(feature = "ristretto255"))] 76 | //! # type CipherSuite = p256::NistP256; 77 | //! use rand::rngs::OsRng; 78 | //! use rand::RngCore; 79 | //! use voprf::OprfClient; 80 | //! 81 | //! let mut client_rng = OsRng; 82 | //! let client_blind_result = OprfClient::::blind(b"input", &mut client_rng) 83 | //! .expect("Unable to construct client"); 84 | //! ``` 85 | //! 86 | //! ### Server Blind Evaluation 87 | //! 88 | //! In the second step, the server takes as input the message from 89 | //! [OprfClient::blind] (a [BlindedElement]), and runs 90 | //! [OprfServer::blind_evaluate] to produce [EvaluationElement] to be sent to 91 | //! the client. 92 | //! 93 | //! ``` 94 | //! # #[cfg(feature = "ristretto255")] 95 | //! # type CipherSuite = voprf::Ristretto255; 96 | //! # #[cfg(not(feature = "ristretto255"))] 97 | //! # type CipherSuite = p256::NistP256; 98 | //! # use voprf::OprfClient; 99 | //! # use rand::{rngs::OsRng, RngCore}; 100 | //! # 101 | //! # let mut client_rng = OsRng; 102 | //! # let client_blind_result = OprfClient::::blind( 103 | //! # b"input", 104 | //! # &mut client_rng, 105 | //! # ).expect("Unable to construct client"); 106 | //! # use voprf::OprfServer; 107 | //! # let mut server_rng = OsRng; 108 | //! # let server = OprfServer::::new(&mut server_rng).unwrap(); 109 | //! let server_evaluate_result = server.blind_evaluate(&client_blind_result.message); 110 | //! ``` 111 | //! 112 | //! ### Client Finalization 113 | //! 114 | //! In the final step on the client side, the client takes as input the message 115 | //! from [OprfServer::evaluate] (an [EvaluationElement]), and runs 116 | //! [OprfClient::finalize] to produce an output for the protocol. 117 | //! 118 | //! ``` 119 | //! # #[cfg(feature = "ristretto255")] 120 | //! # type CipherSuite = voprf::Ristretto255; 121 | //! # #[cfg(not(feature = "ristretto255"))] 122 | //! # type CipherSuite = p256::NistP256; 123 | //! # use voprf::OprfClient; 124 | //! # use rand::{rngs::OsRng, RngCore}; 125 | //! # 126 | //! # let mut client_rng = OsRng; 127 | //! # let client_blind_result = OprfClient::::blind( 128 | //! # b"input", 129 | //! # &mut client_rng, 130 | //! # ).expect("Unable to construct client"); 131 | //! # use voprf::OprfServer; 132 | //! # let mut server_rng = OsRng; 133 | //! # let server = OprfServer::::new(&mut server_rng).unwrap(); 134 | //! # let message = server.blind_evaluate(&client_blind_result.message); 135 | //! let client_finalize_result = client_blind_result 136 | //! .state 137 | //! .finalize(b"input", &message) 138 | //! .expect("Unable to perform client finalization"); 139 | //! 140 | //! println!("VOPRF output: {:?}", client_finalize_result.to_vec()); 141 | //! ``` 142 | //! 143 | //! ### Server Evaluation 144 | //! 145 | //! Optionally, if the server has direct access to the PRF input, then it need 146 | //! not perform the oblivious computation and can simply run 147 | //! [OprfServer::evaluate] to generate an output which matches the output 148 | //! produced by an execution of the oblivious protocol on the same input and 149 | //! key. 150 | //! 151 | //! ``` 152 | //! # #[cfg(feature = "ristretto255")] 153 | //! # type CipherSuite = voprf::Ristretto255; 154 | //! # #[cfg(not(feature = "ristretto255"))] 155 | //! # type CipherSuite = p256::NistP256; 156 | //! # use voprf::OprfClient; 157 | //! # use rand::{rngs::OsRng, RngCore}; 158 | //! # 159 | //! # let mut client_rng = OsRng; 160 | //! # let client_blind_result = OprfClient::::blind( 161 | //! # b"input", 162 | //! # &mut client_rng, 163 | //! # ).expect("Unable to construct client"); 164 | //! # use voprf::OprfServer; 165 | //! # let mut server_rng = OsRng; 166 | //! # let server = OprfServer::::new(&mut server_rng).unwrap(); 167 | //! # let message = server.blind_evaluate(&client_blind_result.message); 168 | //! let client_finalize_result = client_blind_result 169 | //! .state 170 | //! .finalize(b"input", &message) 171 | //! .expect("Unable to perform client finalization"); 172 | //! 173 | //! let server_evaluate_result = server 174 | //! .evaluate(b"input") 175 | //! .expect("Unable to perform the server evaluation"); 176 | //! 177 | //! assert_eq!(client_finalize_result, server_evaluate_result); 178 | //! ``` 179 | //! 180 | //! ## Verifiable Mode 181 | //! 182 | //! In verifiable mode, a [VoprfClient] interacts with a [VoprfServer] to 183 | //! compute the output of the VOPRF. In order to verify the server's 184 | //! computation, the client checks a server-generated proof against the server's 185 | //! public key. If the proof fails to verify, then the client does not receive 186 | //! an output. 187 | //! 188 | //! In batch mode, a single proof can be used for multiple VOPRF evaluations. 189 | //! See [the batching section](#batching) for more details on how to perform 190 | //! batch evaluations. 191 | //! 192 | //! ### Server Setup 193 | //! 194 | //! The protocol begins with a setup phase, in which the server must run 195 | //! [VoprfServer::new()] to produce an instance of itself. This instance must be 196 | //! persisted on the server and used for online client evaluations. 197 | //! 198 | //! ``` 199 | //! # #[cfg(feature = "ristretto255")] 200 | //! # type CipherSuite = voprf::Ristretto255; 201 | //! # #[cfg(not(feature = "ristretto255"))] 202 | //! # type CipherSuite = p256::NistP256; 203 | //! use rand::rngs::OsRng; 204 | //! use rand::RngCore; 205 | //! use voprf::VoprfServer; 206 | //! 207 | //! let mut server_rng = OsRng; 208 | //! let server = VoprfServer::::new(&mut server_rng).unwrap(); 209 | //! 210 | //! // To be sent to the client 211 | //! println!("Server public key: {:?}", server.get_public_key()); 212 | //! ``` 213 | //! 214 | //! The public key should be sent to the client, since the client will need it 215 | //! in the final step of the protocol in order to complete the evaluation of the 216 | //! VOPRF. 217 | //! 218 | //! ### Client Blinding 219 | //! 220 | //! In the first step, the client chooses an input, and runs 221 | //! [VoprfClient::blind] to produce a [VoprfClientBlindResult], which consists 222 | //! of a [BlindedElement] to be sent to the server and a [VoprfClient] which 223 | //! must be persisted on the client for the final step of the VOPRF protocol. 224 | //! 225 | //! ``` 226 | //! # #[cfg(feature = "ristretto255")] 227 | //! # type CipherSuite = voprf::Ristretto255; 228 | //! # #[cfg(not(feature = "ristretto255"))] 229 | //! # type CipherSuite = p256::NistP256; 230 | //! use rand::rngs::OsRng; 231 | //! use rand::RngCore; 232 | //! use voprf::VoprfClient; 233 | //! 234 | //! let mut client_rng = OsRng; 235 | //! let client_blind_result = VoprfClient::::blind(b"input", &mut client_rng) 236 | //! .expect("Unable to construct client"); 237 | //! ``` 238 | //! 239 | //! ### Server Blind Evaluation 240 | //! 241 | //! In the second step, the server takes as input the message from 242 | //! [VoprfClient::blind] (a [BlindedElement]), and runs 243 | //! [VoprfServer::blind_evaluate] to produce a [VoprfServerEvaluateResult], 244 | //! which consists of an [EvaluationElement] to be sent to the client along with 245 | //! a proof. 246 | //! 247 | //! ``` 248 | //! # #[cfg(feature = "ristretto255")] 249 | //! # type CipherSuite = voprf::Ristretto255; 250 | //! # #[cfg(not(feature = "ristretto255"))] 251 | //! # type CipherSuite = p256::NistP256; 252 | //! # use voprf::{VoprfServerEvaluateResult, VoprfClient}; 253 | //! # use rand::{rngs::OsRng, RngCore}; 254 | //! # 255 | //! # let mut client_rng = OsRng; 256 | //! # let client_blind_result = VoprfClient::::blind( 257 | //! # b"input", 258 | //! # &mut client_rng, 259 | //! # ).expect("Unable to construct client"); 260 | //! # use voprf::VoprfServer; 261 | //! # let mut server_rng = OsRng; 262 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 263 | //! let VoprfServerEvaluateResult { message, proof } = 264 | //! server.blind_evaluate(&mut server_rng, &client_blind_result.message); 265 | //! ``` 266 | //! 267 | //! ### Client Finalization 268 | //! 269 | //! In the final step, the client takes as input the message from 270 | //! [VoprfServer::blind_evaluate] (an [EvaluationElement]), the proof, and the 271 | //! server's public key, and runs [VoprfClient::finalize] to produce an output 272 | //! for the protocol. 273 | //! 274 | //! ``` 275 | //! # #[cfg(feature = "ristretto255")] 276 | //! # type CipherSuite = voprf::Ristretto255; 277 | //! # #[cfg(not(feature = "ristretto255"))] 278 | //! # type CipherSuite = p256::NistP256; 279 | //! # use voprf::VoprfClient; 280 | //! # use rand::{rngs::OsRng, RngCore}; 281 | //! # 282 | //! # let mut client_rng = OsRng; 283 | //! # let client_blind_result = VoprfClient::::blind( 284 | //! # b"input", 285 | //! # &mut client_rng, 286 | //! # ).expect("Unable to construct client"); 287 | //! # use voprf::VoprfServer; 288 | //! # let mut server_rng = OsRng; 289 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 290 | //! # let server_evaluate_result = server.blind_evaluate( 291 | //! # &mut server_rng, 292 | //! # &client_blind_result.message, 293 | //! # ); 294 | //! let client_finalize_result = client_blind_result 295 | //! .state 296 | //! .finalize( 297 | //! b"input", 298 | //! &server_evaluate_result.message, 299 | //! &server_evaluate_result.proof, 300 | //! server.get_public_key(), 301 | //! ) 302 | //! .expect("Unable to perform client finalization"); 303 | //! 304 | //! println!("VOPRF output: {:?}", client_finalize_result.to_vec()); 305 | //! ``` 306 | //! 307 | //! ### Server Evaluation 308 | //! 309 | //! Optionally, if the server has direct access to the PRF input, then it need 310 | //! not perform the oblivious computation and can simply run 311 | //! [VoprfServer::evaluate] to generate an output which matches the output 312 | //! produced by an execution of the oblivious protocol on the same input and 313 | //! key. 314 | //! 315 | //! ``` 316 | //! # #[cfg(feature = "ristretto255")] 317 | //! # type CipherSuite = voprf::Ristretto255; 318 | //! # #[cfg(not(feature = "ristretto255"))] 319 | //! # type CipherSuite = p256::NistP256; 320 | //! # use voprf::VoprfClient; 321 | //! # use rand::{rngs::OsRng, RngCore}; 322 | //! # 323 | //! # let mut client_rng = OsRng; 324 | //! # let client_blind_result = VoprfClient::::blind( 325 | //! # b"input", 326 | //! # &mut client_rng, 327 | //! # ).expect("Unable to construct client"); 328 | //! # use voprf::VoprfServer; 329 | //! # let mut server_rng = OsRng; 330 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 331 | //! # let server_evaluate_result = server.blind_evaluate( 332 | //! # &mut server_rng, 333 | //! # &client_blind_result.message, 334 | //! # ); 335 | //! let client_finalize_result = client_blind_result 336 | //! .state 337 | //! .finalize( 338 | //! b"input", 339 | //! &server_evaluate_result.message, 340 | //! &server_evaluate_result.proof, 341 | //! server.get_public_key(), 342 | //! ) 343 | //! .expect("Unable to perform client finalization"); 344 | //! 345 | //! let server_evaluate_result = server 346 | //! .evaluate(b"input") 347 | //! .expect("Unable to perform the server evaluation"); 348 | //! 349 | //! assert_eq!(client_finalize_result, server_evaluate_result); 350 | //! ``` 351 | //! 352 | //! # Advanced Usage 353 | //! 354 | //! There are two additional (and optional) extensions to the core VOPRF 355 | //! protocol: support for batching of evaluations, and support for public 356 | //! metadata. 357 | //! 358 | //! ## Batching 359 | //! 360 | //! It is sometimes desirable to generate only a single, constant-size proof for 361 | //! an unbounded number of VOPRF evaluations (on arbitrary inputs). 362 | //! [VoprfClient] and [VoprfServer] support a batch API for handling this case. 363 | //! In the following example, we show how to use the batch API to produce a 364 | //! single proof for 10 parallel VOPRF evaluations. 365 | //! 366 | //! First, the client produces 10 blindings, storing their resulting states and 367 | //! messages: 368 | //! 369 | //! ``` 370 | //! # #[cfg(feature = "ristretto255")] 371 | //! # type CipherSuite = voprf::Ristretto255; 372 | //! # #[cfg(not(feature = "ristretto255"))] 373 | //! # type CipherSuite = p256::NistP256; 374 | //! # use voprf::VoprfClient; 375 | //! # use rand::{rngs::OsRng, RngCore}; 376 | //! # 377 | //! let mut client_rng = OsRng; 378 | //! let mut client_states = vec![]; 379 | //! let mut client_messages = vec![]; 380 | //! for _ in 0..10 { 381 | //! let client_blind_result = VoprfClient::::blind(b"input", &mut client_rng) 382 | //! .expect("Unable to construct client"); 383 | //! client_states.push(client_blind_result.state); 384 | //! client_messages.push(client_blind_result.message); 385 | //! } 386 | //! ``` 387 | //! 388 | //! Next, the server calls the [VoprfServer::batch_blind_evaluate_prepare] and 389 | //! [VoprfServer::batch_blind_evaluate_finish] function on a set of client 390 | //! messages, to produce a corresponding set of messages to be returned to the 391 | //! client (returned in the same order), along with a single proof: 392 | //! 393 | //! ``` 394 | //! # #[cfg(feature = "ristretto255")] 395 | //! # type CipherSuite = voprf::Ristretto255; 396 | //! # #[cfg(not(feature = "ristretto255"))] 397 | //! # type CipherSuite = p256::NistP256; 398 | //! # use voprf::{VoprfServerBatchEvaluateFinishResult, VoprfClient}; 399 | //! # use rand::{rngs::OsRng, RngCore}; 400 | //! # 401 | //! # let mut client_rng = OsRng; 402 | //! # let mut client_states = vec![]; 403 | //! # let mut client_messages = vec![]; 404 | //! # for _ in 0..10 { 405 | //! # let client_blind_result = VoprfClient::::blind( 406 | //! # b"input", 407 | //! # &mut client_rng, 408 | //! # ).expect("Unable to construct client"); 409 | //! # client_states.push(client_blind_result.state); 410 | //! # client_messages.push(client_blind_result.message); 411 | //! # } 412 | //! # use voprf::VoprfServer; 413 | //! let mut server_rng = OsRng; 414 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 415 | //! let prepared_evaluation_elements = server.batch_blind_evaluate_prepare(client_messages.iter()); 416 | //! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect(); 417 | //! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server 418 | //! .batch_blind_evaluate_finish(&mut server_rng, client_messages.iter(), &prepared_elements) 419 | //! .expect("Unable to perform server batch evaluate"); 420 | //! let messages: Vec<_> = messages.collect(); 421 | //! ``` 422 | //! 423 | //! If `alloc` is available, `VoprfServer::batch_blind_evaluate` can be called 424 | //! to avoid having to collect output manually: 425 | //! 426 | //! ``` 427 | //! # #[cfg(feature = "alloc")] { 428 | //! # #[cfg(feature = "ristretto255")] 429 | //! # type CipherSuite = voprf::Ristretto255; 430 | //! # #[cfg(not(feature = "ristretto255"))] 431 | //! # type CipherSuite = p256::NistP256; 432 | //! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient}; 433 | //! # use rand::{rngs::OsRng, RngCore}; 434 | //! # 435 | //! # let mut client_rng = OsRng; 436 | //! # let mut client_states = vec![]; 437 | //! # let mut client_messages = vec![]; 438 | //! # for _ in 0..10 { 439 | //! # let client_blind_result = VoprfClient::::blind( 440 | //! # b"input", 441 | //! # &mut client_rng, 442 | //! # ).expect("Unable to construct client"); 443 | //! # client_states.push(client_blind_result.state); 444 | //! # client_messages.push(client_blind_result.message); 445 | //! # } 446 | //! # use voprf::VoprfServer; 447 | //! let mut server_rng = OsRng; 448 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 449 | //! let VoprfServerBatchEvaluateResult { messages, proof } = server 450 | //! .batch_blind_evaluate(&mut server_rng, &client_messages) 451 | //! .expect("Unable to perform server batch evaluate"); 452 | //! # } 453 | //! ``` 454 | //! 455 | //! Then, the client calls [VoprfClient::batch_finalize] on the client states 456 | //! saved from the first step, along with the messages returned by the server, 457 | //! along with the server's proof, in order to produce a vector of outputs if 458 | //! the proof verifies correctly. 459 | //! 460 | //! ``` 461 | //! # #[cfg(feature = "alloc")] { 462 | //! # #[cfg(feature = "ristretto255")] 463 | //! # type CipherSuite = voprf::Ristretto255; 464 | //! # #[cfg(not(feature = "ristretto255"))] 465 | //! # type CipherSuite = p256::NistP256; 466 | //! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient}; 467 | //! # use rand::{rngs::OsRng, RngCore}; 468 | //! # 469 | //! # let mut client_rng = OsRng; 470 | //! # let mut client_states = vec![]; 471 | //! # let mut client_messages = vec![]; 472 | //! # for _ in 0..10 { 473 | //! # let client_blind_result = VoprfClient::::blind( 474 | //! # b"input", 475 | //! # &mut client_rng, 476 | //! # ).expect("Unable to construct client"); 477 | //! # client_states.push(client_blind_result.state); 478 | //! # client_messages.push(client_blind_result.message); 479 | //! # } 480 | //! # use voprf::VoprfServer; 481 | //! # let mut server_rng = OsRng; 482 | //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); 483 | //! # let VoprfServerBatchEvaluateResult { messages, proof } = server 484 | //! # .batch_blind_evaluate(&mut server_rng, &client_messages) 485 | //! # .expect("Unable to perform server batch evaluate"); 486 | //! let client_batch_finalize_result = VoprfClient::batch_finalize( 487 | //! &[b"input"; 10], 488 | //! &client_states, 489 | //! &messages, 490 | //! &proof, 491 | //! server.get_public_key(), 492 | //! ) 493 | //! .expect("Unable to perform client batch finalization") 494 | //! .collect::>(); 495 | //! 496 | //! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result); 497 | //! # } 498 | //! ``` 499 | //! 500 | //! ## Metadata 501 | //! 502 | //! The optional metadata parameter included in the POPRF mode allows clients 503 | //! and servers to cryptographically bind additional data to the VOPRF output. 504 | //! This metadata is known to both parties at the start of the protocol, and is 505 | //! inserted under the server's blind evaluate step and the client's finalize 506 | //! step. This metadata can be constructed with some type of higher-level domain 507 | //! separation to avoid cross-protocol attacks or related issues. 508 | //! 509 | //! The API for POPRF mode is similar to VOPRF mode, except that a [PoprfServer] 510 | //! and [PoprfClient] are used, and that each of the functions accept an 511 | //! additional (and optional) info parameter which represents the public input. 512 | //! See 513 | //! 514 | //! for more detailed information on how this public input should be used. 515 | //! 516 | //! # Features 517 | //! 518 | //! - The `alloc` feature requires Rust's `alloc` crate and enables batching 519 | //! VOPRF evaluations. 520 | //! 521 | //! - The `serde` feature, enabled by default, provides convenience functions 522 | //! for serializing and deserializing with [serde](https://serde.rs/). 523 | //! 524 | //! - The `danger` feature, disabled by default, exposes functions for setting 525 | //! and getting internal values not available in the default API. These 526 | //! functions are intended for use in by higher-level cryptographic protocols 527 | //! that need access to these raw values and are able to perform the necessary 528 | //! validations on them (such as being valid group elements). 529 | //! 530 | //! - The `ristretto255-ciphersuite` features enables using [`Ristretto255`] as 531 | //! a [`CipherSuite`]. 532 | //! 533 | //! - The `ristretto255` feature enables using [`Ristretto255`] as the 534 | //! underlying group for the [Group] choice. To select a specific backend see 535 | //! the [curve25519-dalek] documentation. 536 | //! 537 | //! [curve25519-dalek]: 538 | //! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends) 539 | 540 | #![no_std] 541 | #![cfg_attr(docsrs, feature(doc_cfg))] 542 | #![cfg_attr(not(test), deny(unsafe_code))] 543 | #![warn( 544 | clippy::cargo, 545 | clippy::missing_errors_doc, 546 | missing_debug_implementations, 547 | missing_docs 548 | )] 549 | #![allow(clippy::multiple_crate_versions)] 550 | 551 | #[cfg(any(feature = "alloc", test))] 552 | extern crate alloc; 553 | 554 | #[cfg(feature = "std")] 555 | extern crate std; 556 | 557 | mod ciphersuite; 558 | mod common; 559 | mod error; 560 | mod group; 561 | mod oprf; 562 | mod poprf; 563 | mod serialization; 564 | mod voprf; 565 | 566 | #[cfg(test)] 567 | mod tests; 568 | 569 | // Exports 570 | 571 | pub use crate::ciphersuite::CipherSuite; 572 | #[cfg(feature = "danger")] 573 | pub use crate::common::derive_key; 574 | pub use crate::common::{ 575 | BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, 576 | }; 577 | pub use crate::error::{Error, InternalError, Result}; 578 | pub use crate::group::Group; 579 | #[cfg(feature = "ristretto255")] 580 | pub use crate::group::Ristretto255; 581 | pub use crate::oprf::{OprfClient, OprfClientBlindResult, OprfServer}; 582 | #[cfg(feature = "alloc")] 583 | pub use crate::poprf::PoprfServerBatchEvaluateResult; 584 | pub use crate::poprf::{ 585 | PoprfClient, PoprfClientBatchFinalizeResult, PoprfPreparedTweak, PoprfServer, 586 | PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluateFinishedMessages, 587 | PoprfServerBatchEvaluatePrepareResult, PoprfServerBatchEvaluatePreparedEvaluationElements, 588 | }; 589 | pub use crate::serialization::{ 590 | BlindedElementLen, EvaluationElementLen, OprfClientLen, OprfServerLen, PoprfClientLen, 591 | PoprfServerLen, ProofLen, VoprfClientLen, VoprfServerLen, 592 | }; 593 | #[cfg(feature = "alloc")] 594 | pub use crate::voprf::VoprfServerBatchEvaluateResult; 595 | pub use crate::voprf::{ 596 | VoprfClient, VoprfClientBatchFinalizeResult, VoprfClientBlindResult, VoprfServer, 597 | VoprfServerBatchEvaluateFinishResult, VoprfServerBatchEvaluateFinishedMessages, 598 | VoprfServerBatchEvaluatePreparedEvaluationElements, VoprfServerEvaluateResult, 599 | }; 600 | -------------------------------------------------------------------------------- /src/voprf.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Contains the main VOPRF API 10 | 11 | #[cfg(feature = "alloc")] 12 | use alloc::vec::Vec; 13 | use core::iter::{self, Map, Repeat, Zip}; 14 | 15 | use derive_where::derive_where; 16 | use digest::{Digest, Output}; 17 | use generic_array::typenum::Unsigned; 18 | use generic_array::GenericArray; 19 | use rand_core::{CryptoRng, RngCore}; 20 | 21 | use crate::common::{ 22 | derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, 23 | server_evaluate_hash_input, verify_proof, BlindedElement, EvaluationElement, Mode, 24 | PreparedEvaluationElement, Proof, STR_FINALIZE, 25 | }; 26 | #[cfg(feature = "serde")] 27 | use crate::serialization::serde::{Element, Scalar}; 28 | use crate::{CipherSuite, Error, Group, Result}; 29 | 30 | //////////////////////////// 31 | // High-level API Structs // 32 | // ====================== // 33 | //////////////////////////// 34 | 35 | /// A client which engages with a [VoprfServer] in verifiable mode, meaning 36 | /// that the OPRF outputs can be checked against a server public key. 37 | #[derive_where(Clone, ZeroizeOnDrop)] 38 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] 39 | #[cfg_attr( 40 | feature = "serde", 41 | derive(serde::Deserialize, serde::Serialize), 42 | serde(bound = "") 43 | )] 44 | pub struct VoprfClient { 45 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 46 | pub(crate) blind: ::Scalar, 47 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 48 | pub(crate) blinded_element: ::Elem, 49 | } 50 | 51 | /// A server which engages with a [VoprfClient] in verifiable mode, meaning 52 | /// that the OPRF outputs can be checked against a server public key. 53 | #[derive_where(Clone, ZeroizeOnDrop)] 54 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] 55 | #[cfg_attr( 56 | feature = "serde", 57 | derive(serde::Deserialize, serde::Serialize), 58 | serde(bound = "") 59 | )] 60 | pub struct VoprfServer { 61 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 62 | pub(crate) sk: ::Scalar, 63 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 64 | pub(crate) pk: ::Elem, 65 | } 66 | 67 | ///////////////////////// 68 | // API Implementations // 69 | // =================== // 70 | ///////////////////////// 71 | 72 | impl VoprfClient { 73 | /// Computes the first step for the multiplicative blinding version of 74 | /// DH-OPRF. 75 | /// 76 | /// # Errors 77 | /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 78 | pub fn blind( 79 | input: &[u8], 80 | blinding_factor_rng: &mut R, 81 | ) -> Result> { 82 | let blind = CS::Group::random_scalar(blinding_factor_rng); 83 | Self::deterministic_blind_unchecked_inner(input, blind) 84 | } 85 | 86 | /// Computes the first step for the multiplicative blinding version of 87 | /// DH-OPRF, taking a blinding factor scalar as input instead of sampling 88 | /// from an RNG. 89 | /// 90 | /// # Caution 91 | /// 92 | /// This should be used with caution, since it does not perform any checks 93 | /// on the validity of the blinding factor! 94 | /// 95 | /// # Errors 96 | /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 97 | #[cfg(any(feature = "danger", test))] 98 | pub fn deterministic_blind_unchecked( 99 | input: &[u8], 100 | blind: ::Scalar, 101 | ) -> Result> { 102 | Self::deterministic_blind_unchecked_inner(input, blind) 103 | } 104 | 105 | /// Can only fail with [`Error::Input`]. 106 | fn deterministic_blind_unchecked_inner( 107 | input: &[u8], 108 | blind: ::Scalar, 109 | ) -> Result> { 110 | let blinded_element = deterministic_blind_unchecked::(input, &blind, Mode::Voprf)?; 111 | Ok(VoprfClientBlindResult { 112 | state: Self { 113 | blind, 114 | blinded_element, 115 | }, 116 | message: BlindedElement(blinded_element), 117 | }) 118 | } 119 | 120 | /// Computes the third step for the multiplicative blinding version of 121 | /// DH-OPRF, in which the client unblinds the server's message. 122 | /// 123 | /// # Errors 124 | /// - [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. 125 | /// - [`Error::ProofVerification`] if the `proof` failed to verify. 126 | pub fn finalize( 127 | &self, 128 | input: &[u8], 129 | evaluation_element: &EvaluationElement, 130 | proof: &Proof, 131 | pk: ::Elem, 132 | ) -> Result> { 133 | let inputs = core::array::from_ref(&input); 134 | let clients = core::array::from_ref(self); 135 | let messages = core::array::from_ref(evaluation_element); 136 | 137 | let mut batch_result = Self::batch_finalize(inputs, clients, messages, proof, pk)?; 138 | batch_result.next().unwrap() 139 | } 140 | 141 | /// Allows for batching of the finalization of multiple [VoprfClient] 142 | /// and [EvaluationElement] pairs 143 | /// 144 | /// # Errors 145 | /// - [`Error::Batch`] if the number of `clients` and `messages` don't match 146 | /// or is longer then [`u16::MAX`]. 147 | /// - [`Error::ProofVerification`] if the `proof` failed to verify. 148 | /// 149 | /// The resulting messages can each fail individually with [`Error::Input`] 150 | /// if the `input` is empty or longer then [`u16::MAX`]. 151 | pub fn batch_finalize<'a, I, II, IC, IM>( 152 | inputs: &'a II, 153 | clients: &'a IC, 154 | messages: &'a IM, 155 | proof: &Proof, 156 | pk: ::Elem, 157 | ) -> Result> 158 | where 159 | CS: 'a, 160 | I: 'a + AsRef<[u8]>, 161 | &'a II: 'a + IntoIterator, 162 | <&'a II as IntoIterator>::IntoIter: ExactSizeIterator, 163 | &'a IC: 'a + IntoIterator>, 164 | <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, 165 | &'a IM: 'a + IntoIterator>, 166 | <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, 167 | { 168 | let unblinded_elements = verifiable_unblind(clients, messages, pk, proof)?; 169 | let inputs_and_unblinded_elements = inputs.into_iter().zip(unblinded_elements); 170 | Ok(finalize_after_unblind::( 171 | inputs_and_unblinded_elements, 172 | )) 173 | } 174 | 175 | /// Only used for test functions 176 | #[cfg(test)] 177 | pub fn from_blind_and_element( 178 | blind: ::Scalar, 179 | blinded_element: ::Elem, 180 | ) -> Self { 181 | Self { 182 | blind, 183 | blinded_element, 184 | } 185 | } 186 | 187 | /// Only used for test functions 188 | #[cfg(test)] 189 | pub fn get_blind(&self) -> ::Scalar { 190 | self.blind 191 | } 192 | } 193 | 194 | impl VoprfServer { 195 | /// Produces a new instance of a [VoprfServer] using a supplied RNG 196 | /// 197 | /// # Errors 198 | /// [`Error::Protocol`] if the protocol fails and can't be completed. 199 | pub fn new(rng: &mut R) -> Result { 200 | let mut seed = GenericArray::<_, ::ScalarLen>::default(); 201 | rng.fill_bytes(&mut seed); 202 | // This can't fail as the hash output is type constrained. 203 | Self::new_from_seed(&seed, &[]) 204 | } 205 | 206 | /// Produces a new instance of a [VoprfServer] using a supplied set of 207 | /// bytes to represent the server's private key 208 | /// 209 | /// # Errors 210 | /// [`Error::Deserialization`] if the private key is not a valid point on 211 | /// the group or zero. 212 | pub fn new_with_key(key: &[u8]) -> Result { 213 | let sk = CS::Group::deserialize_scalar(key)?; 214 | let pk = CS::Group::base_elem() * &sk; 215 | Ok(Self { sk, pk }) 216 | } 217 | 218 | /// Produces a new instance of a [VoprfServer] using a supplied set of 219 | /// bytes which are used as a seed to derive the server's private key. 220 | /// 221 | /// Corresponds to DeriveKeyPair() function from the VOPRF specification. 222 | /// 223 | /// # Errors 224 | /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer 225 | /// then `u16::MAX - 3`. 226 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 227 | pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result { 228 | let (sk, pk) = derive_keypair::(seed, info, Mode::Voprf)?; 229 | Ok(Self { sk, pk }) 230 | } 231 | 232 | /// Only used for tests 233 | #[cfg(test)] 234 | pub fn get_private_key(&self) -> ::Scalar { 235 | self.sk 236 | } 237 | 238 | /// Computes the second step for the multiplicative blinding version of 239 | /// DH-OPRF. This message is sent from the server (who holds the OPRF key) 240 | /// to the client. 241 | pub fn blind_evaluate( 242 | &self, 243 | rng: &mut R, 244 | blinded_element: &BlindedElement, 245 | ) -> VoprfServerEvaluateResult { 246 | let mut prepared_evaluation_elements = 247 | self.batch_blind_evaluate_prepare(iter::once(blinded_element)); 248 | let prepared_evaluation_element = [prepared_evaluation_elements.next().unwrap()]; 249 | 250 | // This can't fail because we know the size of the inputs. 251 | let VoprfServerBatchEvaluateFinishResult { 252 | mut messages, 253 | proof, 254 | } = self 255 | .batch_blind_evaluate_finish( 256 | rng, 257 | iter::once(blinded_element), 258 | &prepared_evaluation_element, 259 | ) 260 | .unwrap(); 261 | 262 | let message = messages.next().unwrap(); 263 | 264 | VoprfServerEvaluateResult { message, proof } 265 | } 266 | 267 | /// Allows for batching of the evaluation of multiple [BlindedElement] 268 | /// messages from a [VoprfClient] 269 | /// 270 | /// # Errors 271 | /// [`Error::Batch`] if the number of `blinded_elements` and 272 | /// `evaluation_elements` don't match or is longer then [`u16::MAX`] 273 | #[cfg(feature = "alloc")] 274 | pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, I>( 275 | &self, 276 | rng: &mut R, 277 | blinded_elements: &'a I, 278 | ) -> Result> 279 | where 280 | CS: 'a, 281 | &'a I: IntoIterator>, 282 | <&'a I as IntoIterator>::IntoIter: ExactSizeIterator, 283 | { 284 | let prepared_evaluation_elements = self 285 | .batch_blind_evaluate_prepare(blinded_elements.into_iter()) 286 | .collect(); 287 | let VoprfServerBatchEvaluateFinishResult { messages, proof } = self 288 | .batch_blind_evaluate_finish::<_, _, Vec<_>>( 289 | rng, 290 | blinded_elements.into_iter(), 291 | &prepared_evaluation_elements, 292 | )?; 293 | let messages = messages.collect(); 294 | 295 | Ok(VoprfServerBatchEvaluateResult { messages, proof }) 296 | } 297 | 298 | /// Alternative version of `batch_blind_evaluate` without memory allocation. 299 | /// Returned [`PreparedEvaluationElement`] have to be 300 | /// [`collect`](Iterator::collect)ed and passed into 301 | /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish). 302 | pub fn batch_blind_evaluate_prepare<'a, I: Iterator>>( 303 | &self, 304 | blinded_elements: I, 305 | ) -> VoprfServerBatchEvaluatePreparedEvaluationElements 306 | where 307 | CS: 'a, 308 | { 309 | blinded_elements 310 | .zip(iter::repeat(self.sk)) 311 | .map(|(blinded_element, sk)| { 312 | PreparedEvaluationElement(EvaluationElement(blinded_element.0 * &sk)) 313 | }) 314 | } 315 | 316 | /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare) 317 | /// for more details. 318 | /// 319 | /// # Errors 320 | /// [`Error::Batch`] if the number of `blinded_elements` and 321 | /// `evaluation_elements` don't match or is longer then [`u16::MAX`] 322 | pub fn batch_blind_evaluate_finish< 323 | 'a, 324 | 'b, 325 | R: RngCore + CryptoRng, 326 | IB: Iterator> + ExactSizeIterator, 327 | IE, 328 | >( 329 | &self, 330 | rng: &mut R, 331 | blinded_elements: IB, 332 | evaluation_elements: &'b IE, 333 | ) -> Result> 334 | where 335 | CS: 'a + 'b, 336 | &'b IE: IntoIterator>, 337 | <&'b IE as IntoIterator>::IntoIter: ExactSizeIterator, 338 | { 339 | let g = CS::Group::base_elem(); 340 | let proof = generate_proof( 341 | rng, 342 | self.sk, 343 | g, 344 | self.pk, 345 | blinded_elements.map(|element| element.0), 346 | evaluation_elements.into_iter().map(|element| element.0 .0), 347 | Mode::Voprf, 348 | )?; 349 | 350 | let messages = evaluation_elements.into_iter().map(, 352 | ) -> EvaluationElement>::from( 353 | |element| EvaluationElement(element.0 .0), 354 | )); 355 | 356 | Ok(VoprfServerBatchEvaluateFinishResult { messages, proof }) 357 | } 358 | 359 | /// Computes the output of the POPRF on the server side 360 | /// 361 | /// # Errors 362 | /// [`Error::Input`] if the `input` is longer then [`u16::MAX`]. 363 | pub fn evaluate(&self, input: &[u8]) -> Result::Hash>> { 364 | let input_element = hash_to_group::(input, Mode::Voprf)?; 365 | if CS::Group::is_identity_elem(input_element).into() { 366 | return Err(Error::Input); 367 | }; 368 | let evaluated_element = input_element * &self.sk; 369 | 370 | let issued_element = CS::Group::serialize_elem(evaluated_element); 371 | 372 | server_evaluate_hash_input::(input, None, issued_element) 373 | } 374 | 375 | /// Retrieves the server's public key 376 | pub fn get_public_key(&self) -> ::Elem { 377 | self.pk 378 | } 379 | } 380 | 381 | ///////////////////////// 382 | // Convenience Structs // 383 | //==================== // 384 | ///////////////////////// 385 | 386 | /// Contains the fields that are returned by a verifiable client blind 387 | #[derive_where(Debug; ::Scalar, ::Elem)] 388 | pub struct VoprfClientBlindResult { 389 | /// The state to be persisted on the client 390 | pub state: VoprfClient, 391 | /// The message to send to the server 392 | pub message: BlindedElement, 393 | } 394 | 395 | /// Concrete return type for [`VoprfClient::batch_finalize`]. 396 | pub type VoprfClientBatchFinalizeResult<'a, C, I, II, IC, IM> = FinalizeAfterUnblindResult< 397 | 'a, 398 | C, 399 | I, 400 | Zip<<&'a II as IntoIterator>::IntoIter, VoprfUnblindResult<'a, C, IC, IM>>, 401 | >; 402 | 403 | /// Contains the fields that are returned by a verifiable server evaluate 404 | #[derive_where(Debug; ::Scalar, ::Elem)] 405 | pub struct VoprfServerEvaluateResult { 406 | /// The message to send to the client 407 | pub message: EvaluationElement, 408 | /// The proof for the client to verify 409 | pub proof: Proof, 410 | } 411 | 412 | /// Contains the fields that are returned by a verifiable server batch evaluate 413 | #[derive_where(Debug; ::Scalar, ::Elem)] 414 | #[cfg(feature = "alloc")] 415 | pub struct VoprfServerBatchEvaluateResult { 416 | /// The messages to send to the client 417 | pub messages: Vec>, 418 | /// The proof for the client to verify 419 | pub proof: Proof, 420 | } 421 | 422 | /// Concrete type of [`EvaluationElement`]s returned by 423 | /// [`VoprfServer::batch_blind_evaluate_prepare`]. 424 | pub type VoprfServerBatchEvaluatePreparedEvaluationElements = Map< 425 | Zip::Group as Group>::Scalar>>, 426 | fn( 427 | ( 428 | &BlindedElement, 429 | <::Group as Group>::Scalar, 430 | ), 431 | ) -> PreparedEvaluationElement, 432 | >; 433 | 434 | /// Concrete type of [`EvaluationElement`]s in 435 | /// [`VoprfServerBatchEvaluateFinishResult`]. 436 | pub type VoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map< 437 | <&'a I as IntoIterator>::IntoIter, 438 | fn(&PreparedEvaluationElement) -> EvaluationElement, 439 | >; 440 | 441 | /// Contains the fields that are returned by a verifiable server batch evaluate 442 | /// finish. 443 | #[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, ::Scalar)] 444 | pub struct VoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I> 445 | where 446 | &'a I: IntoIterator>, 447 | { 448 | /// The [`EvaluationElement`]s to send to the client 449 | pub messages: VoprfServerBatchEvaluateFinishedMessages<'a, CS, I>, 450 | /// The proof for the client to verify 451 | pub proof: Proof, 452 | } 453 | 454 | ///////////////////// 455 | // Inner functions // 456 | // =============== // 457 | ///////////////////// 458 | 459 | type VoprfUnblindResult<'a, CS, IC, IM> = Map< 460 | Zip< 461 | Map< 462 | <&'a IC as IntoIterator>::IntoIter, 463 | fn(&VoprfClient) -> <::Group as Group>::Scalar, 464 | >, 465 | <&'a IM as IntoIterator>::IntoIter, 466 | >, 467 | fn( 468 | ( 469 | <::Group as Group>::Scalar, 470 | &EvaluationElement, 471 | ), 472 | ) -> <::Group as Group>::Elem, 473 | >; 474 | 475 | /// Can only fail with [`Error::Batch] or [`Error::ProofVerification`]. 476 | fn verifiable_unblind<'a, CS: 'a + CipherSuite, IC, IM>( 477 | clients: &'a IC, 478 | messages: &'a IM, 479 | pk: ::Elem, 480 | proof: &Proof, 481 | ) -> Result> 482 | where 483 | &'a IC: 'a + IntoIterator>, 484 | <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, 485 | &'a IM: 'a + IntoIterator>, 486 | <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, 487 | { 488 | let g = CS::Group::base_elem(); 489 | 490 | let blinds = clients 491 | .into_iter() 492 | // Convert to `fn` pointer to make a return type possible. 493 | .map() -> _>::from(|x| x.blind)); 494 | let evaluation_elements = messages.into_iter().map(|element| element.0); 495 | let blinded_elements = clients.into_iter().map(|client| client.blinded_element); 496 | 497 | verify_proof( 498 | g, 499 | pk, 500 | blinded_elements, 501 | evaluation_elements, 502 | proof, 503 | Mode::Voprf, 504 | )?; 505 | 506 | Ok(blinds 507 | .zip(messages) 508 | .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) 509 | } 510 | 511 | type FinalizeAfterUnblindResult<'a, C, I, IE> = Map< 512 | IE, 513 | fn((I, <::Group as Group>::Elem)) -> Result::Hash>>, 514 | >; 515 | 516 | /// Returned values can only fail with [`Error::Input`]. 517 | fn finalize_after_unblind< 518 | 'a, 519 | CS: CipherSuite, 520 | I: AsRef<[u8]>, 521 | IE: 'a + Iterator::Elem)>, 522 | >( 523 | inputs_and_unblinded_elements: IE, 524 | ) -> FinalizeAfterUnblindResult<'a, CS, I, IE> { 525 | inputs_and_unblinded_elements.map(|(input, unblinded_element)| { 526 | let elem_len = ::ElemLen::U16.to_be_bytes(); 527 | 528 | // hashInput = I2OSP(len(input), 2) || input || 529 | // I2OSP(len(unblindedElement), 2) || unblindedElement || 530 | // "Finalize" 531 | // return Hash(hashInput) 532 | Ok(CS::Hash::new() 533 | .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?) 534 | .chain_update(input.as_ref()) 535 | .chain_update(elem_len) 536 | .chain_update(CS::Group::serialize_elem(unblinded_element)) 537 | .chain_update(STR_FINALIZE) 538 | .finalize()) 539 | }) 540 | } 541 | 542 | /////////// 543 | // Tests // 544 | // ===== // 545 | /////////// 546 | 547 | #[cfg(test)] 548 | mod tests { 549 | use core::ptr; 550 | 551 | use ::alloc::vec; 552 | use ::alloc::vec::Vec; 553 | use rand::rngs::OsRng; 554 | 555 | use super::*; 556 | use crate::common::{Dst, STR_HASH_TO_GROUP}; 557 | use crate::Group; 558 | 559 | fn prf( 560 | input: &[u8], 561 | key: ::Scalar, 562 | mode: Mode, 563 | ) -> Output { 564 | let dst = Dst::new::(STR_HASH_TO_GROUP, mode); 565 | let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); 566 | 567 | let res = point * &key; 568 | 569 | finalize_after_unblind::(iter::once((input, res))) 570 | .next() 571 | .unwrap() 572 | .unwrap() 573 | } 574 | 575 | fn verifiable_retrieval() { 576 | let input = b"input"; 577 | let mut rng = OsRng; 578 | let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); 579 | let server = VoprfServer::::new(&mut rng).unwrap(); 580 | let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); 581 | let client_finalize_result = client_blind_result 582 | .state 583 | .finalize( 584 | input, 585 | &server_result.message, 586 | &server_result.proof, 587 | server.get_public_key(), 588 | ) 589 | .unwrap(); 590 | let res2 = prf::(input, server.get_private_key(), Mode::Voprf); 591 | assert_eq!(client_finalize_result, res2); 592 | } 593 | 594 | fn verifiable_batch_retrieval() { 595 | let mut rng = OsRng; 596 | let mut inputs = vec![]; 597 | let mut client_states = vec![]; 598 | let mut client_messages = vec![]; 599 | let num_iterations = 10; 600 | for _ in 0..num_iterations { 601 | let mut input = [0u8; 32]; 602 | rng.fill_bytes(&mut input); 603 | let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); 604 | inputs.push(input); 605 | client_states.push(client_blind_result.state); 606 | client_messages.push(client_blind_result.message); 607 | } 608 | let server = VoprfServer::::new(&mut rng).unwrap(); 609 | let prepared_evaluation_elements: Vec<_> = server 610 | .batch_blind_evaluate_prepare(client_messages.iter()) 611 | .collect(); 612 | let VoprfServerBatchEvaluateFinishResult { messages, proof } = server 613 | .batch_blind_evaluate_finish( 614 | &mut rng, 615 | client_messages.iter(), 616 | &prepared_evaluation_elements, 617 | ) 618 | .unwrap(); 619 | let messages: Vec<_> = messages.collect(); 620 | let client_finalize_result = VoprfClient::batch_finalize( 621 | &inputs, 622 | &client_states, 623 | &messages, 624 | &proof, 625 | server.get_public_key(), 626 | ) 627 | .unwrap() 628 | .collect::>>() 629 | .unwrap(); 630 | let mut res2 = vec![]; 631 | for input in inputs.iter().take(num_iterations) { 632 | let output = prf::(input, server.get_private_key(), Mode::Voprf); 633 | res2.push(output); 634 | } 635 | assert_eq!(client_finalize_result, res2); 636 | } 637 | 638 | fn verifiable_batch_bad_public_key() { 639 | let mut rng = OsRng; 640 | let mut inputs = vec![]; 641 | let mut client_states = vec![]; 642 | let mut client_messages = vec![]; 643 | let num_iterations = 10; 644 | for _ in 0..num_iterations { 645 | let mut input = [0u8; 32]; 646 | rng.fill_bytes(&mut input); 647 | let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); 648 | inputs.push(input); 649 | client_states.push(client_blind_result.state); 650 | client_messages.push(client_blind_result.message); 651 | } 652 | let server = VoprfServer::::new(&mut rng).unwrap(); 653 | let prepared_evaluation_elements: Vec<_> = server 654 | .batch_blind_evaluate_prepare(client_messages.iter()) 655 | .collect(); 656 | let VoprfServerBatchEvaluateFinishResult { messages, proof } = server 657 | .batch_blind_evaluate_finish( 658 | &mut rng, 659 | client_messages.iter(), 660 | &prepared_evaluation_elements, 661 | ) 662 | .unwrap(); 663 | let messages: Vec<_> = messages.collect(); 664 | let wrong_pk = { 665 | let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); 666 | // Choose a group element that is unlikely to be the right public key 667 | CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() 668 | }; 669 | let client_finalize_result = 670 | VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk); 671 | assert!(client_finalize_result.is_err()); 672 | } 673 | 674 | fn verifiable_bad_public_key() { 675 | let input = b"input"; 676 | let mut rng = OsRng; 677 | let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); 678 | let server = VoprfServer::::new(&mut rng).unwrap(); 679 | let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); 680 | let wrong_pk = { 681 | let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); 682 | // Choose a group element that is unlikely to be the right public key 683 | CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() 684 | }; 685 | let client_finalize_result = client_blind_result.state.finalize( 686 | input, 687 | &server_result.message, 688 | &server_result.proof, 689 | wrong_pk, 690 | ); 691 | assert!(client_finalize_result.is_err()); 692 | } 693 | 694 | fn verifiable_server_evaluate() { 695 | let input = b"input"; 696 | let mut rng = OsRng; 697 | let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); 698 | let server = VoprfServer::::new(&mut rng).unwrap(); 699 | let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); 700 | 701 | let client_finalize = client_blind_result 702 | .state 703 | .finalize( 704 | input, 705 | &server_result.message, 706 | &server_result.proof, 707 | server.get_public_key(), 708 | ) 709 | .unwrap(); 710 | 711 | // We expect the outputs from client and server to be equal given an identical 712 | // input 713 | let server_evaluate = server.evaluate(input).unwrap(); 714 | assert_eq!(client_finalize, server_evaluate); 715 | 716 | // We expect the outputs from client and server to be different given different 717 | // inputs 718 | let wrong_input = b"wrong input"; 719 | let server_evaluate = server.evaluate(wrong_input).unwrap(); 720 | assert!(client_finalize != server_evaluate); 721 | } 722 | 723 | fn zeroize_voprf_client() { 724 | let input = b"input"; 725 | let mut rng = OsRng; 726 | let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); 727 | 728 | let mut state = client_blind_result.state; 729 | unsafe { ptr::drop_in_place(&mut state) }; 730 | assert!(state.serialize().iter().all(|&x| x == 0)); 731 | 732 | let mut message = client_blind_result.message; 733 | unsafe { ptr::drop_in_place(&mut message) }; 734 | assert!(message.serialize().iter().all(|&x| x == 0)); 735 | } 736 | 737 | fn zeroize_voprf_server() { 738 | let input = b"input"; 739 | let mut rng = OsRng; 740 | let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); 741 | let server = VoprfServer::::new(&mut rng).unwrap(); 742 | let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); 743 | 744 | let mut state = server; 745 | unsafe { ptr::drop_in_place(&mut state) }; 746 | assert!(state.serialize().iter().all(|&x| x == 0)); 747 | 748 | let mut message = server_result.message; 749 | unsafe { ptr::drop_in_place(&mut message) }; 750 | assert!(message.serialize().iter().all(|&x| x == 0)); 751 | 752 | let mut proof = server_result.proof; 753 | unsafe { ptr::drop_in_place(&mut proof) }; 754 | assert!(proof.serialize().iter().all(|&x| x == 0)); 755 | } 756 | 757 | #[test] 758 | fn test_functionality() -> Result<()> { 759 | use p256::NistP256; 760 | use p384::NistP384; 761 | use p521::NistP521; 762 | 763 | #[cfg(feature = "ristretto255")] 764 | { 765 | use crate::Ristretto255; 766 | 767 | verifiable_retrieval::(); 768 | verifiable_batch_retrieval::(); 769 | verifiable_bad_public_key::(); 770 | verifiable_batch_bad_public_key::(); 771 | verifiable_server_evaluate::(); 772 | 773 | zeroize_voprf_client::(); 774 | zeroize_voprf_server::(); 775 | } 776 | 777 | verifiable_retrieval::(); 778 | verifiable_batch_retrieval::(); 779 | verifiable_bad_public_key::(); 780 | verifiable_batch_bad_public_key::(); 781 | verifiable_server_evaluate::(); 782 | 783 | zeroize_voprf_client::(); 784 | zeroize_voprf_server::(); 785 | 786 | verifiable_retrieval::(); 787 | verifiable_batch_retrieval::(); 788 | verifiable_bad_public_key::(); 789 | verifiable_batch_bad_public_key::(); 790 | verifiable_server_evaluate::(); 791 | 792 | zeroize_voprf_client::(); 793 | zeroize_voprf_server::(); 794 | 795 | verifiable_retrieval::(); 796 | verifiable_batch_retrieval::(); 797 | verifiable_bad_public_key::(); 798 | verifiable_batch_bad_public_key::(); 799 | verifiable_server_evaluate::(); 800 | 801 | zeroize_voprf_client::(); 802 | zeroize_voprf_server::(); 803 | 804 | Ok(()) 805 | } 806 | } 807 | -------------------------------------------------------------------------------- /src/poprf.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. 2 | // 3 | // This source code is dual-licensed under either the MIT license found in the 4 | // LICENSE-MIT file in the root directory of this source tree or the Apache 5 | // License, Version 2.0 found in the LICENSE-APACHE file in the root directory 6 | // of this source tree. You may select, at your option, one of the above-listed 7 | // licenses. 8 | 9 | //! Contains the main POPRF API 10 | 11 | #[cfg(feature = "alloc")] 12 | use alloc::vec::Vec; 13 | use core::iter::{self, Map, Repeat, Zip}; 14 | 15 | use derive_where::derive_where; 16 | use digest::{Digest, Output, OutputSizeUser}; 17 | use generic_array::typenum::Unsigned; 18 | use generic_array::{ArrayLength, GenericArray}; 19 | use rand_core::{CryptoRng, RngCore}; 20 | 21 | use crate::common::{ 22 | derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, 23 | server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode, 24 | PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO, 25 | }; 26 | #[cfg(feature = "serde")] 27 | use crate::serialization::serde::{Element, Scalar}; 28 | use crate::{CipherSuite, Error, Group, Result}; 29 | 30 | //////////////////////////// 31 | // High-level API Structs // 32 | // ====================== // 33 | //////////////////////////// 34 | 35 | /// A client which engages with a [PoprfServer] in verifiable mode, meaning 36 | /// that the OPRF outputs can be checked against a server public key. 37 | #[derive_where(Clone, ZeroizeOnDrop)] 38 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] 39 | #[cfg_attr( 40 | feature = "serde", 41 | derive(serde::Deserialize, serde::Serialize), 42 | serde(bound = "") 43 | )] 44 | pub struct PoprfClient { 45 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 46 | pub(crate) blind: ::Scalar, 47 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 48 | pub(crate) blinded_element: ::Elem, 49 | } 50 | 51 | /// A server which engages with a [PoprfClient] in verifiable mode, meaning 52 | /// that the OPRF outputs can be checked against a server public key. 53 | #[derive_where(Clone, ZeroizeOnDrop)] 54 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] 55 | #[cfg_attr( 56 | feature = "serde", 57 | derive(serde::Deserialize, serde::Serialize), 58 | serde(bound = "") 59 | )] 60 | pub struct PoprfServer { 61 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 62 | pub(crate) sk: ::Scalar, 63 | #[cfg_attr(feature = "serde", serde(with = "Element::"))] 64 | pub(crate) pk: ::Elem, 65 | } 66 | 67 | ///////////////////////// 68 | // API Implementations // 69 | // =================== // 70 | ///////////////////////// 71 | 72 | impl PoprfClient { 73 | /// Computes the first step for the multiplicative blinding version of 74 | /// DH-OPRF. 75 | /// 76 | /// # Errors 77 | /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. 78 | pub fn blind( 79 | input: &[u8], 80 | blinding_factor_rng: &mut R, 81 | ) -> Result> { 82 | let blind = CS::Group::random_scalar(blinding_factor_rng); 83 | Self::deterministic_blind_unchecked_inner(input, blind) 84 | } 85 | 86 | /// Computes the first step for the multiplicative blinding version of 87 | /// DH-OPRF, taking a blinding factor scalar as input instead of sampling 88 | /// from an RNG. 89 | /// 90 | /// # Caution 91 | /// 92 | /// This should be used with caution, since it does not perform any checks 93 | /// on the validity of the blinding factor! 94 | /// 95 | /// # Errors 96 | /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. 97 | #[cfg(any(feature = "danger", test))] 98 | pub fn deterministic_blind_unchecked( 99 | input: &[u8], 100 | blind: ::Scalar, 101 | ) -> Result> { 102 | Self::deterministic_blind_unchecked_inner(input, blind) 103 | } 104 | 105 | /// Can only fail with [`Error::Input`]. 106 | fn deterministic_blind_unchecked_inner( 107 | input: &[u8], 108 | blind: ::Scalar, 109 | ) -> Result> { 110 | let blinded_element = deterministic_blind_unchecked::(input, &blind, Mode::Poprf)?; 111 | Ok(PoprfClientBlindResult { 112 | state: Self { 113 | blind, 114 | blinded_element, 115 | }, 116 | message: BlindedElement(blinded_element), 117 | }) 118 | } 119 | 120 | /// Computes the third step for the multiplicative blinding version of 121 | /// DH-OPRF, in which the client unblinds the server's message. 122 | /// 123 | /// # Errors 124 | /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. 125 | /// - [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. 126 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 127 | /// - [`Error::ProofVerification`] if the `proof` failed to verify. 128 | pub fn finalize( 129 | &self, 130 | input: &[u8], 131 | evaluation_element: &EvaluationElement, 132 | proof: &Proof, 133 | pk: ::Elem, 134 | info: Option<&[u8]>, 135 | ) -> Result> 136 | where 137 | <::Hash as OutputSizeUser>::OutputSize: ArrayLength, 138 | { 139 | let clients = core::array::from_ref(self); 140 | let messages = core::array::from_ref(evaluation_element); 141 | 142 | let mut batch_result = 143 | Self::batch_finalize(iter::once(input), clients, messages, proof, pk, info)?; 144 | batch_result.next().unwrap() 145 | } 146 | 147 | /// Allows for batching of the finalization of multiple [PoprfClient] 148 | /// and [EvaluationElement] pairs 149 | /// 150 | /// # Errors 151 | /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. 152 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 153 | /// - [`Error::Batch`] if the number of `inputs`, `clients` and `messages` 154 | /// don't match or is longer than [`u16::MAX`]. 155 | /// - [`Error::ProofVerification`] if the `proof` failed to verify. 156 | /// 157 | /// The resulting messages can each fail individually with [`Error::Input`] 158 | /// if the `input` is empty or longer than [`u16::MAX`]. 159 | pub fn batch_finalize<'a, II: 'a + Iterator + ExactSizeIterator, IC, IM>( 160 | inputs: II, 161 | clients: &'a IC, 162 | messages: &'a IM, 163 | proof: &Proof, 164 | pk: ::Elem, 165 | info: Option<&'a [u8]>, 166 | ) -> Result> 167 | where 168 | CS: 'a, 169 | &'a IC: 'a + IntoIterator>, 170 | <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, 171 | &'a IM: 'a + IntoIterator>, 172 | <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, 173 | <::Hash as OutputSizeUser>::OutputSize: ArrayLength, 174 | { 175 | let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?; 176 | 177 | finalize_after_unblind::<'a, CS, _, _>(unblinded_elements, inputs, info) 178 | } 179 | 180 | /// Only used for test functions 181 | #[cfg(test)] 182 | pub fn get_blind(&self) -> ::Scalar { 183 | self.blind 184 | } 185 | } 186 | 187 | impl PoprfServer { 188 | /// Produces a new instance of a [PoprfServer] using a supplied RNG 189 | /// 190 | /// # Errors 191 | /// [`Error::Protocol`] if the protocol fails and can't be completed. 192 | pub fn new(rng: &mut R) -> Result { 193 | let mut seed = GenericArray::<_, ::ScalarLen>::default(); 194 | rng.fill_bytes(&mut seed); 195 | 196 | Self::new_from_seed(&seed, &[]) 197 | } 198 | 199 | /// Produces a new instance of a [PoprfServer] using a supplied set of 200 | /// bytes to represent the server's private key 201 | /// 202 | /// # Errors 203 | /// [`Error::Deserialization`] if the private key is not a valid point on 204 | /// the group or zero. 205 | pub fn new_with_key(key: &[u8]) -> Result { 206 | let sk = CS::Group::deserialize_scalar(key)?; 207 | let pk = CS::Group::base_elem() * &sk; 208 | Ok(Self { sk, pk }) 209 | } 210 | 211 | /// Produces a new instance of a [PoprfServer] using a supplied set of 212 | /// bytes which are used as a seed to derive the server's private key. 213 | /// 214 | /// Corresponds to DeriveKeyPair() function from the VOPRF specification. 215 | /// 216 | /// # Errors 217 | /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer 218 | /// then `u16::MAX - 3`. 219 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 220 | pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result { 221 | let (sk, pk) = derive_keypair::(seed, info, Mode::Poprf)?; 222 | Ok(Self { sk, pk }) 223 | } 224 | 225 | /// Only used for tests 226 | #[cfg(test)] 227 | pub fn get_private_key(&self) -> ::Scalar { 228 | self.sk 229 | } 230 | 231 | /// Computes the second step for the multiplicative blinding version of 232 | /// DH-OPRF. This message is sent from the server (who holds the OPRF key) 233 | /// to the client. 234 | /// 235 | /// # Errors 236 | /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. 237 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 238 | pub fn blind_evaluate( 239 | &self, 240 | rng: &mut R, 241 | blinded_element: &BlindedElement, 242 | info: Option<&[u8]>, 243 | ) -> Result> { 244 | let PoprfServerBatchEvaluatePrepareResult { 245 | mut prepared_evaluation_elements, 246 | prepared_tweak, 247 | } = self.batch_blind_evaluate_prepare(iter::once(blinded_element), info)?; 248 | 249 | let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap(); 250 | let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element); 251 | 252 | let PoprfServerBatchEvaluateFinishResult { 253 | mut messages, 254 | proof, 255 | } = Self::batch_blind_evaluate_finish( 256 | rng, 257 | iter::once(blinded_element), 258 | prepared_evaluation_elements, 259 | &prepared_tweak, 260 | ) 261 | .unwrap(); 262 | 263 | Ok(PoprfServerEvaluateResult { 264 | message: messages.next().unwrap(), 265 | proof, 266 | }) 267 | } 268 | 269 | /// Allows for batching of the evaluation of multiple [BlindedElement] 270 | /// messages from a [PoprfClient] 271 | /// 272 | /// # Errors 273 | /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. 274 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 275 | #[cfg(feature = "alloc")] 276 | pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, IE>( 277 | &self, 278 | rng: &mut R, 279 | blinded_elements: &'a IE, 280 | info: Option<&[u8]>, 281 | ) -> Result> 282 | where 283 | CS: 'a, 284 | &'a IE: 'a + IntoIterator>, 285 | <&'a IE as IntoIterator>::IntoIter: ExactSizeIterator, 286 | { 287 | let PoprfServerBatchEvaluatePrepareResult { 288 | prepared_evaluation_elements, 289 | prepared_tweak, 290 | } = self.batch_blind_evaluate_prepare(blinded_elements.into_iter(), info)?; 291 | 292 | let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect(); 293 | 294 | // This can't fail because we know the size of the inputs. 295 | let PoprfServerBatchEvaluateFinishResult { messages, proof } = 296 | Self::batch_blind_evaluate_finish::<_, _, Vec<_>>( 297 | rng, 298 | blinded_elements.into_iter(), 299 | &prepared_evaluation_elements, 300 | &prepared_tweak, 301 | ) 302 | .unwrap(); 303 | 304 | let messages: Vec<_> = messages.collect(); 305 | 306 | Ok(PoprfServerBatchEvaluateResult { messages, proof }) 307 | } 308 | 309 | /// Alternative version of `batch_blind_evaluate` without 310 | /// memory allocation. Returned [`PreparedEvaluationElement`] have to 311 | /// be [`collect`](Iterator::collect)ed and passed into 312 | /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish). 313 | /// 314 | /// # Errors 315 | /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. 316 | /// - [`Error::Protocol`] if the protocol fails and can't be completed. 317 | pub fn batch_blind_evaluate_prepare<'a, I: Iterator>>( 318 | &self, 319 | blinded_elements: I, 320 | info: Option<&[u8]>, 321 | ) -> Result> 322 | where 323 | CS: 'a, 324 | { 325 | let tweak = compute_tweak::(self.sk, info)?; 326 | 327 | Ok(PoprfServerBatchEvaluatePrepareResult { 328 | prepared_evaluation_elements: blinded_elements.zip(iter::repeat(tweak)).map( 329 | |(blinded_element, tweak)| { 330 | PreparedEvaluationElement(EvaluationElement( 331 | blinded_element.0 * &CS::Group::invert_scalar(tweak), 332 | )) 333 | }, 334 | ), 335 | prepared_tweak: PoprfPreparedTweak(tweak), 336 | }) 337 | } 338 | 339 | /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare) 340 | /// for more details. 341 | /// 342 | /// # Errors 343 | /// [`Error::Batch`] if the number of `blinded_elements` and 344 | /// `prepared_evaluation_elements` don't match or is longer then 345 | /// [`u16::MAX`] 346 | pub fn batch_blind_evaluate_finish< 347 | 'a, 348 | 'b, 349 | R: RngCore + CryptoRng, 350 | IB: Iterator> + ExactSizeIterator, 351 | IE, 352 | >( 353 | rng: &mut R, 354 | blinded_elements: IB, 355 | prepared_evaluation_elements: &'b IE, 356 | prepared_tweak: &PoprfPreparedTweak, 357 | ) -> Result> 358 | where 359 | CS: 'a, 360 | &'b IE: IntoIterator>, 361 | <&'b IE as IntoIterator>::IntoIter: ExactSizeIterator, 362 | { 363 | let g = CS::Group::base_elem(); 364 | let tweak = prepared_tweak.0; 365 | let tweaked_key = g * &tweak; 366 | 367 | let proof = generate_proof( 368 | rng, 369 | tweak, 370 | g, 371 | tweaked_key, 372 | prepared_evaluation_elements 373 | .into_iter() 374 | .map(|element| element.0 .0), 375 | blinded_elements.map(|element| element.0), 376 | Mode::Poprf, 377 | )?; 378 | 379 | let messages = prepared_evaluation_elements.into_iter().map(, 381 | ) -> _>::from( 382 | |element| EvaluationElement(element.0 .0), 383 | )); 384 | 385 | Ok(PoprfServerBatchEvaluateFinishResult { messages, proof }) 386 | } 387 | 388 | /// Computes the output of the VOPRF on the server side 389 | /// 390 | /// # Errors 391 | /// [`Error::Input`] if the `input` is longer then [`u16::MAX`]. 392 | pub fn evaluate( 393 | &self, 394 | input: &[u8], 395 | info: Option<&[u8]>, 396 | ) -> Result::Hash>> { 397 | let input_element = hash_to_group::(input, Mode::Poprf)?; 398 | if CS::Group::is_identity_elem(input_element).into() { 399 | return Err(Error::Input); 400 | }; 401 | 402 | let tweak = compute_tweak::(self.sk, info)?; 403 | 404 | let evaluated_element = input_element * &CS::Group::invert_scalar(tweak); 405 | 406 | let issued_element = CS::Group::serialize_elem(evaluated_element); 407 | 408 | server_evaluate_hash_input::(input, info, issued_element) 409 | } 410 | 411 | /// Retrieves the server's public key 412 | pub fn get_public_key(&self) -> ::Elem { 413 | self.pk 414 | } 415 | } 416 | 417 | impl BlindedElement { 418 | /// Creates a [BlindedElement] from a raw group element. 419 | /// 420 | /// # Caution 421 | /// 422 | /// This should be used with caution, since it does not perform any checks 423 | /// on the validity of the value itself! 424 | #[cfg(feature = "danger")] 425 | pub fn from_value_unchecked(value: ::Elem) -> Self { 426 | Self(value) 427 | } 428 | 429 | /// Exposes the internal value 430 | #[cfg(feature = "danger")] 431 | pub fn value(&self) -> ::Elem { 432 | self.0 433 | } 434 | } 435 | 436 | impl EvaluationElement { 437 | /// Creates an [EvaluationElement] from a raw group element. 438 | /// 439 | /// # Caution 440 | /// 441 | /// This should be used with caution, since it does not perform any checks 442 | /// on the validity of the value itself! 443 | #[cfg(feature = "danger")] 444 | pub fn from_value_unchecked(value: ::Elem) -> Self { 445 | Self(value) 446 | } 447 | 448 | /// Exposes the internal value 449 | #[cfg(feature = "danger")] 450 | pub fn value(&self) -> ::Elem { 451 | self.0 452 | } 453 | } 454 | 455 | ///////////////////////// 456 | // Convenience Structs // 457 | //==================== // 458 | ///////////////////////// 459 | 460 | /// Contains the fields that are returned by a verifiable client blind 461 | #[derive_where(Debug; ::Scalar, ::Elem)] 462 | pub struct PoprfClientBlindResult { 463 | /// The state to be persisted on the client 464 | pub state: PoprfClient, 465 | /// The message to send to the server 466 | pub message: BlindedElement, 467 | } 468 | 469 | /// Concrete return type for [`PoprfClient::batch_finalize`]. 470 | pub type PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM> = 471 | FinalizeAfterUnblindResult<'a, CS, PoprfUnblindResult<'a, CS, IC, IM>, II>; 472 | 473 | /// Contains the fields that are returned by a verifiable server evaluate 474 | #[derive_where(Debug; ::Scalar, ::Elem)] 475 | pub struct PoprfServerEvaluateResult { 476 | /// The message to send to the client 477 | pub message: EvaluationElement, 478 | /// The proof for the client to verify 479 | pub proof: Proof, 480 | } 481 | 482 | /// Contains the fields that are returned by a verifiable server batch evaluate 483 | #[derive_where(Debug; ::Scalar, ::Elem)] 484 | #[cfg(feature = "alloc")] 485 | pub struct PoprfServerBatchEvaluateResult { 486 | /// The messages to send to the client 487 | pub messages: Vec>, 488 | /// The proof for the client to verify 489 | pub proof: Proof, 490 | } 491 | 492 | /// Concrete type of [`EvaluationElement`]s in 493 | /// [`PoprfServerBatchEvaluatePrepareResult`]. 494 | pub type PoprfServerBatchEvaluatePreparedEvaluationElements = Map< 495 | Zip::Group as Group>::Scalar>>, 496 | fn( 497 | ( 498 | &BlindedElement, 499 | <::Group as Group>::Scalar, 500 | ), 501 | ) -> PreparedEvaluationElement, 502 | >; 503 | 504 | /// Prepared tweak by a partially verifiable server batch evaluate prepare. 505 | #[derive_where(Clone, ZeroizeOnDrop)] 506 | #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar)] 507 | #[cfg_attr( 508 | feature = "serde", 509 | derive(serde::Deserialize, serde::Serialize), 510 | serde(bound = "") 511 | )] 512 | pub struct PoprfPreparedTweak( 513 | #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] 514 | ::Scalar, 515 | ); 516 | 517 | /// Contains the fields that are returned by a partially verifiable server batch 518 | /// evaluate prepare 519 | #[derive_where(Debug; I, ::Scalar)] 520 | pub struct PoprfServerBatchEvaluatePrepareResult { 521 | /// Prepared [`EvaluationElement`]. 522 | pub prepared_evaluation_elements: PoprfServerBatchEvaluatePreparedEvaluationElements, 523 | /// Prepared tweak. 524 | pub prepared_tweak: PoprfPreparedTweak, 525 | } 526 | 527 | /// Concrete type of [`EvaluationElement`]s in 528 | /// [`PoprfServerBatchEvaluateFinishResult`]. 529 | pub type PoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map< 530 | <&'a I as IntoIterator>::IntoIter, 531 | fn(&PreparedEvaluationElement) -> EvaluationElement, 532 | >; 533 | 534 | /// Contains the fields that are returned by a verifiable server batch evaluate 535 | /// finish. 536 | #[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, ::Scalar)] 537 | pub struct PoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I> 538 | where 539 | &'a I: IntoIterator>, 540 | { 541 | /// The [`EvaluationElement`]s to send to the client 542 | pub messages: PoprfServerBatchEvaluateFinishedMessages<'a, CS, I>, 543 | /// The proof for the client to verify 544 | pub proof: Proof, 545 | } 546 | 547 | ///////////////////// 548 | // Inner functions // 549 | // =============== // 550 | ///////////////////// 551 | 552 | /// Inner function for POPRF blind. Computes the tweaked key from the server 553 | /// public key and info. 554 | /// 555 | /// Can only fail with [`Error::Info`] or [`Error::Protocol`] 556 | fn compute_tweaked_key( 557 | pk: ::Elem, 558 | info: Option<&[u8]>, 559 | ) -> Result<::Elem> { 560 | // None for info is treated the same as empty bytes 561 | let info = info.unwrap_or_default(); 562 | 563 | // framedInfo = "Info" || I2OSP(len(info), 2) || info 564 | // m = G.HashToScalar(framedInfo) 565 | // T = G.ScalarBaseMult(m) 566 | // tweakedKey = T + pkS 567 | // if tweakedKey == G.Identity(): 568 | // raise InvalidInputError 569 | let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; 570 | let framed_info = [STR_INFO.as_slice(), &info_len, info]; 571 | 572 | let dst = Dst::new::(STR_HASH_TO_SCALAR, Mode::Poprf); 573 | // This can't fail, the size of the `input` is known. 574 | let m = CS::Group::hash_to_scalar::(&framed_info, &dst.as_dst()).unwrap(); 575 | 576 | let t = CS::Group::base_elem() * &m; 577 | let tweaked_key = t + &pk; 578 | 579 | // Check if resulting element 580 | match bool::from(CS::Group::is_identity_elem(tweaked_key)) { 581 | true => Err(Error::Protocol), 582 | false => Ok(tweaked_key), 583 | } 584 | } 585 | 586 | /// Inner function for POPRF evaluate. Computes the tweak from the server 587 | /// private key and info. 588 | /// 589 | /// Can only fail with [`Error::Info`] and [`Error::Protocol`]. 590 | fn compute_tweak( 591 | sk: ::Scalar, 592 | info: Option<&[u8]>, 593 | ) -> Result<::Scalar> { 594 | // None for info is treated the same as empty bytes 595 | let info = info.unwrap_or_default(); 596 | 597 | // framedInfo = "Info" || I2OSP(len(info), 2) || info 598 | // m = G.HashToScalar(framedInfo) 599 | // t = skS + m 600 | // if t == 0: 601 | // raise InverseError 602 | let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; 603 | let framed_info = [STR_INFO.as_slice(), &info_len, info]; 604 | 605 | let dst = Dst::new::(STR_HASH_TO_SCALAR, Mode::Poprf); 606 | // This can't fail, the size of the `input` is known. 607 | let m = CS::Group::hash_to_scalar::(&framed_info, &dst.as_dst()).unwrap(); 608 | 609 | let t = sk + &m; 610 | 611 | // Check if resulting element is equal to zero 612 | match bool::from(CS::Group::is_zero_scalar(t)) { 613 | true => Err(Error::Protocol), 614 | false => Ok(t), 615 | } 616 | } 617 | 618 | type PoprfUnblindResult<'a, CS, IC, IM> = Map< 619 | Zip< 620 | Map< 621 | <&'a IC as IntoIterator>::IntoIter, 622 | fn(&PoprfClient) -> <::Group as Group>::Scalar, 623 | >, 624 | <&'a IM as IntoIterator>::IntoIter, 625 | >, 626 | fn( 627 | ( 628 | <::Group as Group>::Scalar, 629 | &'a EvaluationElement, 630 | ), 631 | ) -> <::Group as Group>::Elem, 632 | >; 633 | 634 | /// Can only fail with [`Error::Info`], [`Error::Protocol`], [`Error::Batch] or 635 | /// [`Error::ProofVerification`]. 636 | fn poprf_unblind<'a, CS: 'a + CipherSuite, IC, IM>( 637 | clients: &'a IC, 638 | messages: &'a IM, 639 | pk: ::Elem, 640 | proof: &Proof, 641 | info: Option<&[u8]>, 642 | ) -> Result> 643 | where 644 | &'a IC: 'a + IntoIterator>, 645 | <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, 646 | &'a IM: 'a + IntoIterator>, 647 | <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, 648 | { 649 | let info = info.unwrap_or_default(); 650 | let tweaked_key = compute_tweaked_key::(pk, Some(info))?; 651 | 652 | let g = CS::Group::base_elem(); 653 | 654 | let blinds = clients 655 | .into_iter() 656 | // Convert to `fn` pointer to make a return type possible. 657 | .map() -> _>::from(|x| x.blind)); 658 | let evaluation_elements = messages.into_iter().map(|element| element.0); 659 | let blinded_elements = clients.into_iter().map(|client| client.blinded_element); 660 | 661 | verify_proof( 662 | g, 663 | tweaked_key, 664 | evaluation_elements, 665 | blinded_elements, 666 | proof, 667 | Mode::Poprf, 668 | )?; 669 | 670 | Ok(blinds 671 | .zip(messages) 672 | .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) 673 | } 674 | 675 | type FinalizeAfterUnblindResult<'a, CS, IE, II> = Map< 676 | Zip, Repeat<&'a [u8]>>, 677 | fn( 678 | ((<::Group as Group>::Elem, &[u8]), &[u8]), 679 | ) -> Result::Hash>>, 680 | >; 681 | 682 | /// Can only fail with [`Error::Batch`] and returned values can only fail with 683 | /// [`Error::Info`] or [`Error::Input`] individually. 684 | fn finalize_after_unblind< 685 | 'a, 686 | CS: CipherSuite, 687 | IE: 'a + Iterator::Elem> + ExactSizeIterator, 688 | II: 'a + Iterator + ExactSizeIterator, 689 | >( 690 | unblinded_elements: IE, 691 | inputs: II, 692 | info: Option<&'a [u8]>, 693 | ) -> Result> 694 | where 695 | <::Hash as OutputSizeUser>::OutputSize: ArrayLength, 696 | { 697 | if unblinded_elements.len() != inputs.len() { 698 | return Err(Error::Batch); 699 | } 700 | 701 | let info = info.unwrap_or_default(); 702 | 703 | Ok(unblinded_elements.zip(inputs).zip(iter::repeat(info)).map( 704 | |((unblinded_element, input), info)| { 705 | let elem_len = ::ElemLen::U16.to_be_bytes(); 706 | 707 | // hashInput = I2OSP(len(input), 2) || input || 708 | // I2OSP(len(info), 2) || info || 709 | // I2OSP(len(unblindedElement), 2) || unblindedElement || 710 | // "Finalize" 711 | // return Hash(hashInput) 712 | let output = CS::Hash::new() 713 | .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?) 714 | .chain_update(input.as_ref()) 715 | .chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Info)?) 716 | .chain_update(info.as_ref()) 717 | .chain_update(elem_len) 718 | .chain_update(CS::Group::serialize_elem(unblinded_element)) 719 | .chain_update(STR_FINALIZE) 720 | .finalize(); 721 | 722 | Ok(output) 723 | }, 724 | )) 725 | } 726 | 727 | /////////// 728 | // Tests // 729 | // ===== // 730 | /////////// 731 | 732 | #[cfg(test)] 733 | mod tests { 734 | use core::ptr; 735 | 736 | use rand::rngs::OsRng; 737 | 738 | use super::*; 739 | use crate::common::STR_HASH_TO_GROUP; 740 | use crate::Group; 741 | 742 | fn prf( 743 | input: &[u8], 744 | key: ::Scalar, 745 | info: &[u8], 746 | mode: Mode, 747 | ) -> Output { 748 | let t = compute_tweak::(key, Some(info)).unwrap(); 749 | 750 | let dst = Dst::new::(STR_HASH_TO_GROUP, mode); 751 | let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); 752 | 753 | // evaluatedElement = G.ScalarInverse(t) * blindedElement 754 | let res = point * &CS::Group::invert_scalar(t); 755 | 756 | finalize_after_unblind::(iter::once(res), iter::once(input), Some(info)) 757 | .unwrap() 758 | .next() 759 | .unwrap() 760 | .unwrap() 761 | } 762 | 763 | fn verifiable_retrieval() { 764 | let input = b"input"; 765 | let info = b"info"; 766 | let mut rng = OsRng; 767 | let server = PoprfServer::::new(&mut rng).unwrap(); 768 | let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); 769 | let server_result = server 770 | .blind_evaluate(&mut rng, &client_blind_result.message, Some(info)) 771 | .unwrap(); 772 | let client_finalize_result = client_blind_result 773 | .state 774 | .finalize( 775 | input, 776 | &server_result.message, 777 | &server_result.proof, 778 | server.get_public_key(), 779 | Some(info), 780 | ) 781 | .unwrap(); 782 | let res2 = prf::(input, server.get_private_key(), info, Mode::Poprf); 783 | assert_eq!(client_finalize_result, res2); 784 | } 785 | 786 | fn verifiable_bad_public_key() { 787 | let input = b"input"; 788 | let info = b"info"; 789 | let mut rng = OsRng; 790 | let server = PoprfServer::::new(&mut rng).unwrap(); 791 | let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); 792 | let server_result = server 793 | .blind_evaluate(&mut rng, &client_blind_result.message, Some(info)) 794 | .unwrap(); 795 | let wrong_pk = { 796 | let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); 797 | // Choose a group element that is unlikely to be the right public key 798 | CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() 799 | }; 800 | let client_finalize_result = client_blind_result.state.finalize( 801 | input, 802 | &server_result.message, 803 | &server_result.proof, 804 | wrong_pk, 805 | Some(info), 806 | ); 807 | assert!(client_finalize_result.is_err()); 808 | } 809 | 810 | fn verifiable_server_evaluate() { 811 | let input = b"input"; 812 | let info = Some(b"info".as_slice()); 813 | let mut rng = OsRng; 814 | let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); 815 | let server = PoprfServer::::new(&mut rng).unwrap(); 816 | let server_result = server 817 | .blind_evaluate(&mut rng, &client_blind_result.message, info) 818 | .unwrap(); 819 | 820 | let client_finalize = client_blind_result 821 | .state 822 | .finalize( 823 | input, 824 | &server_result.message, 825 | &server_result.proof, 826 | server.get_public_key(), 827 | info, 828 | ) 829 | .unwrap(); 830 | 831 | // We expect the outputs from client and server to be equal given an identical 832 | // input 833 | let server_evaluate = server.evaluate(input, info).unwrap(); 834 | assert_eq!(client_finalize, server_evaluate); 835 | 836 | // We expect the outputs from client and server to be different given different 837 | // inputs 838 | let wrong_input = b"wrong input"; 839 | let server_evaluate = server.evaluate(wrong_input, info).unwrap(); 840 | assert!(client_finalize != server_evaluate); 841 | } 842 | 843 | fn zeroize_verifiable_client() { 844 | let input = b"input"; 845 | let mut rng = OsRng; 846 | let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); 847 | 848 | let mut state = client_blind_result.state; 849 | unsafe { ptr::drop_in_place(&mut state) }; 850 | assert!(state.serialize().iter().all(|&x| x == 0)); 851 | 852 | let mut message = client_blind_result.message; 853 | unsafe { ptr::drop_in_place(&mut message) }; 854 | assert!(message.serialize().iter().all(|&x| x == 0)); 855 | } 856 | 857 | fn zeroize_verifiable_server() { 858 | let input = b"input"; 859 | let info = b"info"; 860 | let mut rng = OsRng; 861 | let server = PoprfServer::::new(&mut rng).unwrap(); 862 | let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); 863 | let server_result = server 864 | .blind_evaluate(&mut rng, &client_blind_result.message, Some(info)) 865 | .unwrap(); 866 | 867 | let mut state = server; 868 | unsafe { ptr::drop_in_place(&mut state) }; 869 | assert!(state.serialize().iter().all(|&x| x == 0)); 870 | 871 | let mut message = server_result.message; 872 | unsafe { ptr::drop_in_place(&mut message) }; 873 | assert!(message.serialize().iter().all(|&x| x == 0)); 874 | 875 | let mut proof = server_result.proof; 876 | unsafe { ptr::drop_in_place(&mut proof) }; 877 | assert!(proof.serialize().iter().all(|&x| x == 0)); 878 | } 879 | 880 | #[test] 881 | fn test_functionality() -> Result<()> { 882 | use p256::NistP256; 883 | use p384::NistP384; 884 | use p521::NistP521; 885 | 886 | #[cfg(feature = "ristretto255")] 887 | { 888 | use crate::Ristretto255; 889 | 890 | verifiable_retrieval::(); 891 | verifiable_bad_public_key::(); 892 | verifiable_server_evaluate::(); 893 | 894 | zeroize_verifiable_client::(); 895 | zeroize_verifiable_server::(); 896 | } 897 | 898 | verifiable_retrieval::(); 899 | verifiable_bad_public_key::(); 900 | verifiable_server_evaluate::(); 901 | 902 | zeroize_verifiable_client::(); 903 | zeroize_verifiable_server::(); 904 | 905 | verifiable_retrieval::(); 906 | verifiable_bad_public_key::(); 907 | verifiable_server_evaluate::(); 908 | 909 | zeroize_verifiable_client::(); 910 | zeroize_verifiable_server::(); 911 | 912 | verifiable_retrieval::(); 913 | verifiable_bad_public_key::(); 914 | verifiable_server_evaluate::(); 915 | 916 | zeroize_verifiable_client::(); 917 | zeroize_verifiable_server::(); 918 | 919 | Ok(()) 920 | } 921 | } 922 | --------------------------------------------------------------------------------