Compare commits

..
35 Commits
Author SHA1 Message Date
Kevin LewiandGitHub f0531f0812 Publishing v0.5 (#133) 2024-03-06 17:49:35 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c93884aca3 Bump actions/cache from 3 to 4 (#132)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 14:33:57 -08:00
daxpeddaandGitHub 40769f7eca Fix ambiguous lifetime elision (#131) 2024-01-17 12:09:32 -08:00
Kevin LewiandGitHub 1b67086028 Publishing v0.5.0-pre.7 (#128) 2024-01-11 11:58:36 -08:00
daxpeddaandGitHub 68cc7d3709 Test P-521 (#127) 2023-11-12 16:14:52 -08:00
Kevin LewiandGitHub 59e3fedb21 Updating setup-rust-action (#125) 2023-09-21 18:45:22 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8da56845b8 Bump actions/checkout from 3 to 4 (#120)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 14:32:31 -07:00
Kevin LewiandGitHub c0162ec8d9 Fixing clippy IntoIterator warnings (#123) 2023-09-20 01:48:44 -07:00
Kevin LewiandGitHub ee91c9776c Publishing v0.5.0-pre.6 (#118) 2023-07-24 20:45:18 -07:00
daxpeddaandGitHub 0fdfdfdaee Bump curve25519-dalek to v4 (#116) 2023-07-24 16:39:40 -07:00
Kevin LewiandGitHub eafa134c94 Publishing v0.5.0-pre.5 (#115) 2023-06-27 15:10:37 -07:00
daxpeddaandGitHub 209b957ae4 Bump curve25519-dalek to v4.0.0-rc.3 (#113) 2023-06-26 11:09:06 -07:00
Kevin LewiandGitHub f79ebf9844 Updating dual-license language (#110) 2023-05-22 23:04:39 -07:00
Kevin LewiandGitHub 20a35da7ba Publishing 0.5.0-pre.4 (#109) 2023-05-21 00:29:21 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4bd2cf466e Update curve25519-dalek requirement from =4.0.0-rc.1 to =4.0.0-rc.2 (#108)
Updates the requirements on [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek) to permit the latest version.
- [Release notes](https://github.com/dalek-cryptography/curve25519-dalek/releases)
- [Changelog](https://github.com/dalek-cryptography/curve25519-dalek/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dalek-cryptography/curve25519-dalek/compare/4.0.0-rc.1...4.0.0-rc.2)

---
updated-dependencies:
- dependency-name: curve25519-dalek
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-02 11:21:28 +09:00
Kevin LewiandGitHub 829c6add0f Publishing 0.5.0-pre.3 (#107) 2023-03-04 15:09:02 -08:00
daxpeddaandGitHub 8b895cc631 Update RustCrypto dependencies to v0.13 (#106) 2023-03-04 14:54:13 -08:00
daxpeddaandGitHub 83eb78b232 Test P-384 (#84) 2023-02-08 15:48:12 -08:00
daxpeddaandGitHub 5badeff8d2 Update to draft 19 (#101) 2023-02-08 00:18:04 -08:00
Kevin LewiandGitHub 40d81294db Publishing 0.5.0-pre.2 (#104) 2023-02-03 13:26:11 -08:00
daxpeddaandGitHub 8363d26f6f Bump curve25519-dalek to v4.0.0-rc.1 (#102) 2023-02-03 11:19:00 -08:00
daxpeddaandGitHub 5bce3e3206 Use explicit crate features (#100) 2023-02-01 11:37:47 -08:00
daxpeddaandGitHub 2787151e1d Update curve25519-dalek (#94) 2023-01-31 14:19:48 -08:00
daxpeddaandGitHub 0409db6f40 Depend on ProjectivePoint: ToEncodedPoint (#95) 2023-01-31 14:19:33 -08:00
daxpeddaandGitHub 74eaebe446 Fix Clippy (#96) 2023-01-31 10:31:13 -08:00
daxpeddaandGitHub c8de51672b Replace json with serde_json (#92) 2023-01-19 14:17:49 -08:00
daxpeddaandGitHub daa8dc048f Upgrade p256 to v0.12 (#90)
* Upgrade `p256` to v0.12

* Upgrade MSRV to 1.60
2023-01-19 11:11:56 -08:00
Kevin LewiandGitHub 2a351ceb4d Publishing 0.5.0-pre.1 (#88) 2022-12-19 13:17:58 -08:00
Kevin LewiandGitHub 8f60a10b8d Adding all-features CI test (#87) 2022-12-17 18:20:57 -08:00
daxpeddaandGitHub 1691125b09 Update curve25519-dalek to 4.0.0-pre.5 (#86)
* Update `curve25519-dalek`

* Improve documentation
2022-12-17 18:12:23 -08:00
daxpeddaandGitHub 6913b5deaa Fix Clippy (#85) 2022-12-10 14:21:04 -08:00
Kevin LewiandGitHub 2dc6a8b2c2 Publishing v0.4.0 (#83) 2022-09-15 02:18:34 -07:00
raphaelrobertandGitHub f670733165 Updating to draft 11 (#80)
* draft-11

* Fix CI complaints

* Address review comments, CHANGELOG entry, minor fixes
2022-07-09 07:30:20 -04:00
Kevin LewiandGitHub 6e16a99a87 Updating to draft version 10 (#79) 2022-07-01 12:35:21 -07:00
Kevin LewiandGitHub 4646fe4ad0 Exposing the derive_key() function under the danger feature (#73) 2022-04-05 00:41:38 -07:00
25 changed files with 1885 additions and 1432 deletions
+5 -4
View File
@@ -1,6 +1,7 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
+21 -17
View File
@@ -4,7 +4,7 @@ on:
branches: branches:
- main - main
pull_request: pull_request:
types: [opened, repoened, synchronize] types: [opened, reopened, synchronize]
jobs: jobs:
cargo-audit: cargo-audit:
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cache cargo-audit - name: Cache cargo-audit
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/.crates.toml ~/.cargo/.crates.toml
@@ -24,7 +24,7 @@ jobs:
run: cargo install cargo-audit run: cargo install cargo-audit
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Run cargo audit - name: Run cargo audit
run: cargo audit -D warnings run: cargo audit -D warnings
@@ -35,8 +35,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
backend_feature: backend_feature:
- --features ristretto255-ciphersuite,ristretto255-u64 - --features ristretto255-ciphersuite
- --features ristretto255-ciphersuite,ristretto255-u32
- -
frontend_feature: frontend_feature:
- -
@@ -44,11 +43,11 @@ jobs:
- --features serde - --features serde
toolchain: toolchain:
- stable - stable
- 1.57.0 - 1.65.0
name: test name: test
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install ${{ matrix.toolchain }} toolchain - name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -75,6 +74,12 @@ jobs:
command: test command: test
args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }} args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }}
- name: Run cargo test with all features enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
build-no-std: build-no-std:
name: Build with no-std on ${{ matrix.target }} name: Build with no-std on ${{ matrix.target }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -88,15 +93,14 @@ jobs:
- thumbv6m-none-eabi - thumbv6m-none-eabi
backend_feature: backend_feature:
- -
- --features ristretto255-ciphersuite,ristretto255-u64 - --features ristretto255-ciphersuite
- --features ristretto255-ciphersuite,ristretto255-u32
frontend_feature: frontend_feature:
- -
- --features danger - --features danger
- --features serde - --features serde
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v2
- run: rustup target add ${{ matrix.target }} - run: rustup target add ${{ matrix.target }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} - run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }}
@@ -106,7 +110,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install stable toolchain - name: Install stable toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -120,7 +124,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy command: clippy
args: --all-targets -- -D warnings args: --all-features --all-targets -- -D warnings
- name: Run cargo doc - name: Run cargo doc
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
@@ -136,7 +140,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install nightly toolchain - name: Install nightly toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -157,7 +161,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cache - name: Cache
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/.crates.toml ~/.cargo/.crates.toml
@@ -166,10 +170,10 @@ jobs:
key: taplo key: taplo
- name: Install Taplo - name: Install Taplo
run: cargo install taplo-cli run: cargo install taplo-cli --locked
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Run Taplo - name: Run Taplo
run: taplo fmt --check run: taplo fmt --check
+2 -1
View File
@@ -10,9 +10,10 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
rust: [stable]
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v2
with: with:
rust-version: ${{ matrix.rust }} rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master - uses: actions/checkout@master
+37 -2
View File
@@ -1,8 +1,43 @@
# Changelog # Changelog
## 0.4.0-pre.1 (April 1, 2022) ## 0.5.0 (March 6, 2024)
* Updated to be in sync with draft-irtf-cfrg-voprf-09, with * Just a version bump from v0.5.0-pre.7
## 0.5.0-pre.7 (January 11, 2024)
* Updated to be in sync with RFC 9497
## 0.5.0-pre.6 (July 24, 2023)
* Updated curve25519-dalek dependency to 4
## 0.5.0-pre.5 (June 27, 2023)
* Updated curve25519-dalek dependency to 4.0.0-rc.3
## 0.5.0-pre.4 (May 20, 2023)
* Updated curve25519-dalek dependency to 4.0.0-rc.2
## 0.5.0-pre.3 (March 4, 2023)
* Updated to be in sync with draft-irtf-cfrg-voprf-19
* Increased MSRV to 1.65
* Updated p256 dependency to v0.13
* Added p384 tests
## 0.5.0-pre.2 (February 3, 2023)
* Increased MSRV to 1.60
* Updated p256 dependency to v0.12
* Updated curve25519-dalek dependency to 4.0.0-rc.1
## 0.5.0-pre.1 (December 19, 2022)
* Updated curve25519-dalek dependency to 4.0.0-pre.5
## 0.4.0 (September 15, 2022)
* Updated to be in sync with draft-irtf-cfrg-voprf-11, with
the addition of the POPRF mode the addition of the POPRF mode
* Adds the evaluate() function to the servers to calculate the output of the OPRF
directly
* Renames the former evaluate() function to blind_evaluate to match the spec
* Fixes the order of parameters for PoprfClient::blind to align it with the
other clients
* Exposes the derive_key function under the "danger" feature
* Added support for running the API without performing allocations * Added support for running the API without performing allocations
* Revamped the way the Group trait was used, so as to be more easily * Revamped the way the Group trait was used, so as to be more easily
extendable to other groups extendable to other groups
+2 -1
View File
@@ -27,4 +27,5 @@ outlined on that page and do not file a public issue.
## License ## License
By contributing to voprf, you agree that your contributions will be By contributing to voprf, you agree that your contributions will be
licensed under the LICENSE file in the root directory of this source tree. licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
directory of this source tree.
+27 -20
View File
@@ -1,5 +1,5 @@
[package] [package]
authors = ["Kevin Lewi <klewi@fb.com>"] authors = ["Kevin Lewi <lewi.kevin.k@gmail.com>"]
categories = ["no-std", "algorithms", "cryptography"] categories = ["no-std", "algorithms", "cryptography"]
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)" description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
edition = "2021" edition = "2021"
@@ -7,37 +7,35 @@ keywords = ["oprf"]
license = "MIT" license = "MIT"
name = "voprf" name = "voprf"
readme = "README.md" readme = "README.md"
repository = "https://github.com/novifinancial/voprf/" repository = "https://github.com/facebook/voprf/"
rust-version = "1.57" rust-version = "1.65"
version = "0.4.0-pre.1" version = "0.5.0"
[features] [features]
alloc = [] alloc = []
danger = [] danger = []
default = ["ristretto255-ciphersuite", "ristretto255-u64", "serde"] default = ["ristretto255-ciphersuite", "dep:serde"]
ristretto255 = ["curve25519-dalek", "generic-array/more_lengths"] ristretto255 = ["dep:curve25519-dalek", "generic-array/more_lengths"]
ristretto255-ciphersuite = ["ristretto255", "sha2"] ristretto255-ciphersuite = ["ristretto255", "dep:sha2"]
ristretto255-fiat-u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"] serde = ["generic-array/serde", "dep:serde"]
ristretto255-fiat-u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"]
ristretto255-simd = ["curve25519-dalek/simd_backend", "ristretto255"]
ristretto255-u32 = ["curve25519-dalek/u32_backend", "ristretto255"]
ristretto255-u64 = ["curve25519-dalek/u64_backend", "ristretto255"]
serde = ["generic-array/serde", "serde_"]
std = ["alloc"] std = ["alloc"]
[dependencies] [dependencies]
curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true } curve25519-dalek = { version = "4", default-features = false, features = [
derive-where = { version = "=1.0.0-rc.3", features = ["zeroize-on-drop"] } "rand_core",
"zeroize",
], optional = true }
derive-where = { version = "1", features = ["zeroize-on-drop"] }
digest = "0.10" digest = "0.10"
displaydoc = { version = "0.2", default-features = false } displaydoc = { version = "0.2", default-features = false }
elliptic-curve = { version = "=0.12.0-pre.1", features = [ elliptic-curve = { version = "0.13", features = [
"hash2curve", "hash2curve",
"sec1", "sec1",
"voprf", "voprf",
] } ] }
generic-array = "0.14" generic-array = "0.14"
rand_core = { version = "0.6", default-features = false } rand_core = { version = "0.6", default-features = false }
serde_ = { version = "1", package = "serde", default-features = false, features = [ serde = { version = "1", default-features = false, features = [
"derive", "derive",
], optional = true } ], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true } sha2 = { version = "0.10", default-features = false, optional = true }
@@ -47,16 +45,25 @@ zeroize = { version = "1.5", default-features = false }
[dev-dependencies] [dev-dependencies]
generic-array = { version = "0.14", features = ["more_lengths"] } generic-array = { version = "0.14", features = ["more_lengths"] }
hex = "0.4" hex = "0.4"
json = "0.12" p256 = { version = "0.13", default-features = false, features = [
p256 = { version = "=0.11.0-pre.0", default-features = false, features = [ "hash2curve",
"voprf",
] }
p384 = { version = "0.13", default-features = false, features = [
"hash2curve",
"voprf",
] }
p521 = { version = "0.13.3", default-features = false, features = [
"hash2curve", "hash2curve",
"voprf", "voprf",
] } ] }
proptest = "1" proptest = "1"
rand = "0.8" rand = "0.8"
regex = "1" regex = "1"
serde_json = "1"
sha2 = "0.10" sha2 = "0.10"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["danger", "std"] all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = [] targets = []
-12
View File
@@ -1,12 +0,0 @@
## License
Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
+6 -4
View File
@@ -3,7 +3,7 @@ An implementation of a (verifiable) oblivious pseudorandom function (VOPRF)
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. 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.
This implementation is based on the [Internet Draft for VOPRF](https://github.com/cfrg/draft-irtf-cfrg-voprf). This implementation is based on [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497).
Documentation Documentation
------------- -------------
@@ -16,12 +16,12 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`: Add the following line to the dependencies of your `Cargo.toml`:
``` ```
voprf = "0.4.0-pre.1" voprf = "0.5"
``` ```
### Minimum Supported Rust Version ### Minimum Supported Rust Version
Rust **1.57** or higher. Rust **1.65** or higher.
Contributors Contributors
------------ ------------
@@ -32,4 +32,6 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
License License
------- -------
This project is [licensed](./LICENSE) under either Apache 2.0 or MIT, at your option. This project is dual-licensed under either the [MIT license](./LICENSE-MIT)
or the [Apache License, Version 2.0](./LICENSE-APACHE).
You may select, at your option, one of the above-listed licenses.
+11 -10
View File
@@ -1,14 +1,15 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF //! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::{Digest, OutputSizeUser}; use digest::{FixedOutput, HashMarker, OutputSizeUser};
use elliptic_curve::VoprfParameters; use elliptic_curve::VoprfParameters;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
@@ -21,8 +22,8 @@ where
IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>,
{ {
/// The ciphersuite identifier as dictated by /// The ciphersuite identifier as dictated by
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/> /// <https://www.rfc-editor.org/rfc/rfc9497>
const ID: u16; const ID: &'static str;
/// A finite cyclic group along with a point representation that allows some /// A finite cyclic group along with a point representation that allows some
/// customization on how to hash an input to a curve point. See [`Group`]. /// customization on how to hash an input to a curve point. See [`Group`].
@@ -30,17 +31,17 @@ where
/// The main hash function to use (for HKDF computations and hashing /// The main hash function to use (for HKDF computations and hashing
/// transcripts). /// transcripts).
type Hash: BlockSizeUser + Digest; type Hash: BlockSizeUser + Default + FixedOutput + HashMarker;
} }
impl<T: VoprfParameters> CipherSuite for T impl<T: VoprfParameters> CipherSuite for T
where where
T: Group, T: Group,
T::Hash: BlockSizeUser + Digest, T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker,
<T::Hash as OutputSizeUser>::OutputSize: <T::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
{ {
const ID: u16 = T::ID; const ID: &'static str = T::ID;
type Group = T; type Group = T;
+155 -38
View File
@@ -1,19 +1,21 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Common functionality between multiple OPRF modes. //! Common functionality between multiple OPRF modes.
use core::convert::TryFrom; use core::convert::TryFrom;
use core::ops::Add;
use derive_where::derive_where; use derive_where::derive_where;
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::{Digest, OutputSizeUser}; use digest::{Digest, Output, OutputSizeUser};
use generic_array::sequence::Concat; use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U11, U2, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U2, U256, U9};
use generic_array::{ArrayLength, GenericArray}; use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
@@ -33,7 +35,7 @@ pub(crate) const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair";
pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite"; pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite";
pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge"; pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge";
pub(crate) const STR_INFO: [u8; 4] = *b"Info"; pub(crate) const STR_INFO: [u8; 4] = *b"Info";
pub(crate) const STR_VOPRF: [u8; 8] = *b"VOPRF09-"; pub(crate) const STR_OPRF: [u8; 7] = *b"OPRFV1-";
pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-"; pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-";
pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-"; pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-";
@@ -72,7 +74,7 @@ impl Mode {
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct BlindedElement<CS: CipherSuite>( pub struct BlindedElement<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))] #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -89,7 +91,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct EvaluationElement<CS: CipherSuite>( pub struct EvaluationElement<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))] #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -106,7 +108,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>) pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>)
where where
@@ -120,7 +122,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct Proof<CS: CipherSuite> pub struct Proof<CS: CipherSuite>
where where
@@ -153,7 +155,7 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.1 // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?; let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
@@ -194,9 +196,9 @@ where
&STR_CHALLENGE, &STR_CHALLENGE,
]; ];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known. // This can't fail, the size of the `input` is known.
let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap(); let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
let s_scalar = r - &(c_scalar * &k); let s_scalar = r - &(c_scalar * &k);
Ok(Proof { c_scalar, s_scalar }) Ok(Proof { c_scalar, s_scalar })
@@ -216,7 +218,7 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.2 // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.2
let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?; let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?;
let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar); let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar);
let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar); let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar);
@@ -254,9 +256,9 @@ where
&STR_CHALLENGE, &STR_CHALLENGE,
]; ];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known. // This can't fail, the size of the `input` is known.
let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap(); let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
match c.ct_eq(&proof.c_scalar).into() { match c.ct_eq(&proof.c_scalar).into() {
true => Ok(()), true => Ok(()),
@@ -285,7 +287,7 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.1 // https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes(); let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
@@ -296,16 +298,16 @@ where
let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?; let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?;
// seedDST = "Seed-" || contextString // seedDST = "Seed-" || contextString
let seed_dst = GenericArray::from(STR_SEED).concat(create_context_string::<CS>(mode)); let seed_dst = Dst::new::<CS, _, _>(STR_SEED, mode);
// h1Input = I2OSP(len(Bm), 2) || Bm || // h1Input = I2OSP(len(Bm), 2) || Bm ||
// I2OSP(len(seedDST), 2) || seedDST // I2OSP(len(seedDST), 2) || seedDST
// seed = Hash(h1Input) // seed = Hash(h1Input)
let seed = CS::Hash::new() let seed = CS::Hash::new()
.chain_update(&elem_len) .chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(b)) .chain_update(CS::Group::serialize_elem(b))
.chain_update(i2osp_2_array(&seed_dst)) .chain_update(seed_dst.i2osp_2())
.chain_update(seed_dst) .chain_update_multi(&seed_dst.as_dst())
.finalize(); .finalize();
let seed_len = i2osp_2_array(&seed); let seed_len = i2osp_2_array(&seed);
@@ -332,9 +334,9 @@ where
&STR_COMPOSITE, &STR_COMPOSITE,
]; ];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known. // This can't fail, the size of the `input` is known.
let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap(); let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
m = c * &di + &m; m = c * &di + &m;
z = match k_option { z = match k_option {
Some(_) => z, Some(_) => z,
@@ -356,7 +358,7 @@ where
///////////////////// /////////////////////
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`]. /// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
pub(crate) fn derive_key<CS: CipherSuite>( pub(crate) fn derive_key_internal<CS: CipherSuite>(
seed: &[u8], seed: &[u8],
info: &[u8], info: &[u8],
mode: Mode, mode: Mode,
@@ -365,8 +367,7 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
let context_string = create_context_string::<CS>(mode); let dst = Dst::new::<CS, _, _>(STR_DERIVE_KEYPAIR, mode);
let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string);
let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;
@@ -376,7 +377,7 @@ where
// || contextString) // || contextString)
let sk_s = CS::Group::hash_to_scalar::<CS::Hash>( let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
&[seed, &info_len, info, &counter.to_be_bytes()], &[seed, &info_len, info, &counter.to_be_bytes()],
&dst, &dst.as_dst(),
) )
.map_err(|_| Error::DeriveKeyPair)?; .map_err(|_| Error::DeriveKeyPair)?;
@@ -388,6 +389,25 @@ where
Err(Error::Protocol) Err(Error::Protocol)
} }
/// Corresponds to DeriveKeyPair() function from the VOPRF specification.
///
/// # Errors
/// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer
/// then `u16::MAX - 3`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
#[cfg(feature = "danger")]
pub fn derive_key<CS: CipherSuite>(
seed: &[u8],
info: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Scalar, Error>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
derive_key_internal::<CS>(seed, info, mode)
}
type DeriveKeypairResult<CS> = ( type DeriveKeypairResult<CS> = (
<<CS as CipherSuite>::Group as Group>::Scalar, <<CS as CipherSuite>::Group as Group>::Scalar,
<<CS as CipherSuite>::Group as Group>::Elem, <<CS as CipherSuite>::Group as Group>::Elem,
@@ -403,7 +423,7 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
let sk_s = derive_key::<CS>(seed, info, mode)?; let sk_s = derive_key_internal::<CS>(seed, info, mode)?;
let pk_s = CS::Group::base_elem() * &sk_s; let pk_s = CS::Group::base_elem() * &sk_s;
Ok((sk_s, pk_s)) Ok((sk_s, pk_s))
@@ -423,22 +443,119 @@ where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode)); let hashed_point = hash_to_group::<CS>(input, mode)?;
let hashed_point =
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).map_err(|_| Error::Input)?;
Ok(hashed_point * blind) Ok(hashed_point * blind)
} }
/// Generates the contextString parameter as defined in /// Hashes `input` to a point on the curve
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/> pub(crate) fn hash_to_group<CS: CipherSuite>(
pub(crate) fn create_context_string<CS: CipherSuite>(mode: Mode) -> GenericArray<u8, U11> input: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Elem>
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
GenericArray::from(STR_VOPRF) let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
.concat([mode.to_u8()].into()) CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
.concat(CS::ID.to_be_bytes().into()) }
/// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF.
/// Returned values can only fail with [`Error::Input`].
pub(crate) fn server_evaluate_hash_input<CS: CipherSuite>(
input: &[u8],
info: Option<&[u8]>,
issued_element: GenericArray<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
) -> Result<Output<CS::Hash>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
// OPRF & VOPRF
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(issuedElement), 2) || issuedElement ||
// "Finalize"
// return Hash(hashInput)
//
// POPRF
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(info), 2) || info ||
// I2OSP(len(issuedElement), 2) || issuedElement ||
// "Finalize"
let mut hash = CS::Hash::new()
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(input.as_ref());
if let Some(info) = info {
hash = hash
.chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(info.as_ref());
}
Ok(hash
.chain_update(i2osp_2(issued_element.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(issued_element)
.chain_update(STR_FINALIZE)
.finalize())
}
pub(crate) struct Dst<L: ArrayLength<u8>> {
dst_1: GenericArray<u8, L>,
dst_2: &'static str,
}
impl<L: ArrayLength<u8>> Dst<L> {
pub(crate) fn new<CS: CipherSuite, T, TL: ArrayLength<u8>>(par_1: T, mode: Mode) -> Self
where
T: Into<GenericArray<u8, TL>>,
TL: Add<U9, Output = L>,
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let par_1 = par_1.into();
// Generates the contextString parameter as defined in
// <https://www.rfc-editor.org/rfc/rfc9497#section-3.1>
let par_2 = GenericArray::from(STR_OPRF)
.concat([mode.to_u8()].into())
.concat([b'-'].into());
let dst_1 = par_1.concat(par_2);
let dst_2 = CS::ID;
assert!(
L::USIZE + dst_2.len() <= u16::MAX.into(),
"constructed DST longer then {}",
u16::MAX
);
Self { dst_1, dst_2 }
}
pub(crate) fn as_dst(&self) -> [&[u8]; 2] {
[&self.dst_1, self.dst_2.as_bytes()]
}
pub(crate) fn i2osp_2(&self) -> [u8; 2] {
u16::try_from(L::USIZE + self.dst_2.len())
.unwrap()
.to_be_bytes()
}
}
trait DigestExt {
fn chain_update_multi(self, data: &[&[u8]]) -> Self;
}
impl<T> DigestExt for T
where
T: Digest,
{
fn chain_update_multi(mut self, datas: &[&[u8]]) -> Self {
for data in datas {
self.update(data)
}
self
}
} }
/////////////////////// ///////////////////////
+5 -4
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Errors which are produced during an execution of the protocol //! Errors which are produced during an execution of the protocol
+17 -16
View File
@@ -1,17 +1,18 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::Digest; use digest::{FixedOutput, HashMarker};
use elliptic_curve::group::cofactor::CofactorGroup; use elliptic_curve::group::cofactor::CofactorGroup;
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest}; use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}; use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{ use elliptic_curve::{
AffinePoint, Field, FieldSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey, AffinePoint, Field, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey,
}; };
use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::GenericArray; use generic_array::GenericArray;
@@ -24,32 +25,32 @@ impl<C> Group for C
where where
C: GroupDigest, C: GroupDigest,
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>, ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
FieldSize<Self>: ModulusSize, FieldBytesSize<Self>: ModulusSize,
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>, AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
Scalar<Self>: FromOkm, Scalar<Self>: FromOkm,
{ {
type Elem = ProjectivePoint<Self>; type Elem = ProjectivePoint<Self>;
type ElemLen = <FieldSize<Self> as ModulusSize>::CompressedPointSize; type ElemLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
type Scalar = Scalar<Self>; type Scalar = Scalar<Self>;
type ScalarLen = FieldSize<Self>; type ScalarLen = FieldBytesSize<Self>;
// Implements the `hash_to_curve()` function from // Implements the `hash_to_curve()` function from
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 // https://www.rfc-editor.org/rfc/rfc9380.html#section-3
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError> fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>, H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{ {
Self::hash_from_bytes::<ExpandMsgXmd<H>>(input, dst).map_err(|_| InternalError::Input) Self::hash_from_bytes::<ExpandMsgXmd<H>>(input, dst).map_err(|_| InternalError::Input)
} }
// Implements the `HashToScalar()` function // Implements the `HashToScalar()` function
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError> fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>, H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{ {
<Self as GroupDigest>::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst) <Self as GroupDigest>::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
@@ -92,7 +93,7 @@ where
#[cfg(test)] #[cfg(test)]
fn zero_scalar() -> Self::Scalar { fn zero_scalar() -> Self::Scalar {
Scalar::<Self>::zero() Scalar::<Self>::ZERO
} }
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> { fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
@@ -100,7 +101,7 @@ where
} }
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> { fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
SecretKey::<Self>::from_be_bytes(scalar_bits) SecretKey::<Self>::from_slice(scalar_bits)
.map(|secret_key| *secret_key.to_nonzero_scalar()) .map(|secret_key| *secret_key.to_nonzero_scalar())
.map_err(|_| Error::Deserialization) .map_err(|_| Error::Deserialization)
} }
+11 -10
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Defines the Group trait to specify the underlying prime order group //! Defines the Group trait to specify the underlying prime order group
@@ -14,7 +15,7 @@ mod ristretto;
use core::ops::{Add, Mul, Sub}; use core::ops::{Add, Mul, Sub};
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::Digest; use digest::{FixedOutput, HashMarker};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::{ArrayLength, GenericArray}; use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
@@ -26,7 +27,7 @@ use zeroize::Zeroize;
use crate::{InternalError, Result}; use crate::{InternalError, Result};
/// A prime-order subgroup of a base field (EC, prime-order field ...). This /// A prime-order subgroup of a base field (EC, prime-order field ...). This
/// subgroup is noted additively — as in the draft RFC — in this trait. /// subgroup is noted additively — as in the RFC — in this trait.
pub trait Group { pub trait Group {
/// The type of group elements /// The type of group elements
type Elem: ConstantTimeEq type Elem: ConstantTimeEq
@@ -54,9 +55,9 @@ pub trait Group {
/// # Errors /// # Errors
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
/// then [`u16::MAX`]. /// then [`u16::MAX`].
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError> fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>; H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
/// Hashes a slice of pseudo-random bytes to a scalar /// Hashes a slice of pseudo-random bytes to a scalar
@@ -64,9 +65,9 @@ pub trait Group {
/// # Errors /// # Errors
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
/// then [`u16::MAX`]. /// then [`u16::MAX`].
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError> fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>; H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
/// Get the base point for the group /// Get the base point for the group
+19 -25
View File
@@ -1,16 +1,17 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::Identity; use curve25519_dalek::traits::Identity;
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::Digest; use digest::{FixedOutput, HashMarker};
use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
use generic_array::GenericArray; use generic_array::GenericArray;
@@ -22,21 +23,17 @@ use crate::{Error, InternalError, Result};
/// [`Group`] implementation for Ristretto255. /// [`Group`] implementation for Ristretto255.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
#[cfg(feature = "ristretto255")]
pub struct Ristretto255; pub struct Ristretto255;
#[cfg(feature = "ristretto255-ciphersuite")] #[cfg(feature = "ristretto255-ciphersuite")]
impl crate::CipherSuite for Ristretto255 { impl crate::CipherSuite for Ristretto255 {
const ID: u16 = 0x0001; const ID: &'static str = "ristretto255-SHA512";
type Group = Ristretto255; type Group = Ristretto255;
type Hash = sha2::Sha512; type Hash = sha2::Sha512;
} }
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
#[cfg(feature = "ristretto255")]
impl Group for Ristretto255 { impl Group for Ristretto255 {
type Elem = RistrettoPoint; type Elem = RistrettoPoint;
@@ -47,10 +44,10 @@ impl Group for Ristretto255 {
type ScalarLen = U32; type ScalarLen = U32;
// Implements the `hash_to_ristretto255()` function from // Implements the `hash_to_ristretto255()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-B
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError> fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>, H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{ {
let mut uniform_bytes = GenericArray::<_, U64>::default(); let mut uniform_bytes = GenericArray::<_, U64>::default();
@@ -62,10 +59,10 @@ impl Group for Ristretto255 {
} }
// Implements the `HashToScalar()` function from // Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1 // https://www.rfc-editor.org/rfc/rfc9497#section-4.1
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError> fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where where
H: Digest + BlockSizeUser, H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>, H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{ {
let mut uniform_bytes = GenericArray::<_, U64>::default(); let mut uniform_bytes = GenericArray::<_, U64>::default();
@@ -90,11 +87,8 @@ impl Group for Ristretto255 {
} }
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> { fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
if element_bits.len() != 32 {
return Err(Error::Deserialization);
}
CompressedRistretto::from_slice(element_bits) CompressedRistretto::from_slice(element_bits)
.map_err(|_| Error::Deserialization)?
.decompress() .decompress()
.filter(|point| point != &RistrettoPoint::identity()) .filter(|point| point != &RistrettoPoint::identity())
.ok_or(Error::Deserialization) .ok_or(Error::Deserialization)
@@ -104,7 +98,7 @@ impl Group for Ristretto255 {
loop { loop {
let scalar = Scalar::random(rng); let scalar = Scalar::random(rng);
if scalar != Scalar::zero() { if scalar != Scalar::ZERO {
break scalar; break scalar;
} }
} }
@@ -115,12 +109,12 @@ impl Group for Ristretto255 {
} }
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice { fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
scalar.ct_eq(&Scalar::zero()) scalar.ct_eq(&Scalar::ZERO)
} }
#[cfg(test)] #[cfg(test)]
fn zero_scalar() -> Self::Scalar { fn zero_scalar() -> Self::Scalar {
Scalar::zero() Scalar::ZERO
} }
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> { fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
@@ -131,8 +125,8 @@ impl Group for Ristretto255 {
scalar_bits scalar_bits
.try_into() .try_into()
.ok() .ok()
.and_then(Scalar::from_canonical_bytes) .and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
.filter(|scalar| scalar != &Scalar::zero()) .filter(|scalar| scalar != &Scalar::ZERO)
.ok_or(Error::Deserialization) .ok_or(Error::Deserialization)
} }
} }
+13 -4
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Includes a series of tests for the group implementations //! Includes a series of tests for the group implementations
@@ -15,6 +16,8 @@ use crate::{Error, Group, Result};
#[test] #[test]
fn test_group_properties() -> Result<()> { fn test_group_properties() -> Result<()> {
use p256::NistP256; use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")] #[cfg(feature = "ristretto255")]
{ {
@@ -27,6 +30,12 @@ fn test_group_properties() -> Result<()> {
test_identity_element_error::<NistP256>()?; test_identity_element_error::<NistP256>()?;
test_zero_scalar_error::<NistP256>()?; test_zero_scalar_error::<NistP256>()?;
test_identity_element_error::<NistP384>()?;
test_zero_scalar_error::<NistP384>()?;
test_identity_element_error::<NistP521>()?;
test_zero_scalar_error::<NistP521>()?;
Ok(()) Ok(())
} }
+147 -76
View File
@@ -1,16 +1,15 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF) //! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
//! //!
//! Note: This implementation is in sync with //! Note: This implementation is in sync with
//! [draft-irtf-cfrg-voprf-09](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html), //! [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497).
//! but this specification is subject to change, until the final version
//! published by the IETF.
//! //!
//! # Overview //! # Overview
//! //!
@@ -36,18 +35,19 @@
//! VOPRF, where a public input can be supplied to the PRF computation //! VOPRF, where a public input can be supplied to the PRF computation
//! //!
//! In all of these modes, the protocol begins with a client blinding, followed //! In all of these modes, the protocol begins with a client blinding, followed
//! by a server evaluation, and finishes with a client finalization. //! by a server evaluation, and finishes with a client finalization and server
//! evaluation.
//! //!
//! ## Base Mode //! ## Base Mode
//! //!
//! In base mode, an [OprfClient] interacts with an [OprfServer] //! In base mode, an [OprfClient] interacts with an [OprfServer] to compute the
//! to compute the output of the OPRF. //! output of the OPRF.
//! //!
//! ### Server Setup //! ### Server Setup
//! //!
//! The protocol begins with a setup phase, in which the server must run //! The protocol begins with a setup phase, in which the server must run
//! [OprfServer::new()] to produce an instance of itself. This instance //! [OprfServer::new()] to produce an instance of itself. This instance must be
//! must be persisted on the server and used for online client evaluations. //! persisted on the server and used for online client evaluations.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "ristretto255")] //! # #[cfg(feature = "ristretto255")]
@@ -64,11 +64,10 @@
//! //!
//! ### Client Blinding //! ### Client Blinding
//! //!
//! In the first step, the client chooses an input, and runs //! In the first step, the client chooses an input, and runs [OprfClient::blind]
//! [OprfClient::blind] to produce an [OprfClientBlindResult], //! to produce an [OprfClientBlindResult], which consists of a [BlindedElement]
//! which consists of a [BlindedElement] to be sent to the server and an //! to be sent to the server and an [OprfClient] which must be persisted on the
//! [OprfClient] which must be persisted on the client for the final //! client for the final step of the VOPRF protocol.
//! step of the VOPRF protocol.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "ristretto255")] //! # #[cfg(feature = "ristretto255")]
@@ -84,11 +83,11 @@
//! .expect("Unable to construct client"); //! .expect("Unable to construct client");
//! ``` //! ```
//! //!
//! ### Server Evaluation //! ### Server Blind Evaluation
//! //!
//! In the second step, the server takes as input the message from //! In the second step, the server takes as input the message from
//! [OprfClient::blind] (a [BlindedElement]), and runs //! [OprfClient::blind] (a [BlindedElement]), and runs
//! [OprfServer::evaluate] to produce [EvaluationElement] to be sent to //! [OprfServer::blind_evaluate] to produce [EvaluationElement] to be sent to
//! the client. //! the client.
//! //!
//! ``` //! ```
@@ -107,13 +106,13 @@
//! # use voprf::OprfServer; //! # use voprf::OprfServer;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let server_evaluate_result = server.evaluate(&client_blind_result.message); //! let server_evaluate_result = server.blind_evaluate(&client_blind_result.message);
//! ``` //! ```
//! //!
//! ### Client Finalization //! ### Client Finalization
//! //!
//! In the final step, the client takes as input the message from //! In the final step on the client side, the client takes as input the message
//! [OprfServer::evaluate] (an [EvaluationElement]), and runs //! from [OprfServer::evaluate] (an [EvaluationElement]), and runs
//! [OprfClient::finalize] to produce an output for the protocol. //! [OprfClient::finalize] to produce an output for the protocol.
//! //!
//! ``` //! ```
@@ -132,7 +131,7 @@
//! # use voprf::OprfServer; //! # use voprf::OprfServer;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let message = server.evaluate(&client_blind_result.message); //! # let message = server.blind_evaluate(&client_blind_result.message);
//! let client_finalize_result = client_blind_result //! let client_finalize_result = client_blind_result
//! .state //! .state
//! .finalize(b"input", &message) //! .finalize(b"input", &message)
@@ -141,10 +140,47 @@
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec()); //! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ``` //! ```
//! //!
//! ### Server Evaluation
//!
//! Optionally, if the server has direct access to the PRF input, then it need
//! not perform the oblivious computation and can simply run
//! [OprfServer::evaluate] to generate an output which matches the output
//! produced by an execution of the oblivious protocol on the same input and
//! key.
//!
//! ```
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::OprfServer;
//! # let mut server_rng = OsRng;
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let message = server.blind_evaluate(&client_blind_result.message);
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(b"input", &message)
//! .expect("Unable to perform client finalization");
//!
//! let server_evaluate_result = server
//! .evaluate(b"input")
//! .expect("Unable to perform the server evaluation");
//!
//! assert_eq!(client_finalize_result, server_evaluate_result);
//! ```
//!
//! ## Verifiable Mode //! ## Verifiable Mode
//! //!
//! In verifiable mode, a [VoprfClient] interacts with a [VoprfServer] //! In verifiable mode, a [VoprfClient] interacts with a [VoprfServer] to
//! to compute the output of the VOPRF. In order to verify the server's //! compute the output of the VOPRF. In order to verify the server's
//! computation, the client checks a server-generated proof against the server's //! computation, the client checks a server-generated proof against the server's
//! public key. If the proof fails to verify, then the client does not receive //! public key. If the proof fails to verify, then the client does not receive
//! an output. //! an output.
@@ -156,8 +192,8 @@
//! ### Server Setup //! ### Server Setup
//! //!
//! The protocol begins with a setup phase, in which the server must run //! The protocol begins with a setup phase, in which the server must run
//! [VoprfServer::new()] to produce an instance of itself. This instance //! [VoprfServer::new()] to produce an instance of itself. This instance must be
//! must be persisted on the server and used for online client evaluations. //! persisted on the server and used for online client evaluations.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "ristretto255")] //! # #[cfg(feature = "ristretto255")]
@@ -182,10 +218,9 @@
//! ### Client Blinding //! ### Client Blinding
//! //!
//! In the first step, the client chooses an input, and runs //! In the first step, the client chooses an input, and runs
//! [VoprfClient::blind] to produce a [VoprfClientBlindResult], which //! [VoprfClient::blind] to produce a [VoprfClientBlindResult], which consists
//! consists of a [BlindedElement] to be sent to the server and a //! of a [BlindedElement] to be sent to the server and a [VoprfClient] which
//! [VoprfClient] which must be persisted on the client for the final step //! must be persisted on the client for the final step of the VOPRF protocol.
//! of the VOPRF protocol.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "ristretto255")] //! # #[cfg(feature = "ristretto255")]
@@ -201,11 +236,11 @@
//! .expect("Unable to construct client"); //! .expect("Unable to construct client");
//! ``` //! ```
//! //!
//! ### Server Evaluation //! ### Server Blind Evaluation
//! //!
//! In the second step, the server takes as input the message from //! In the second step, the server takes as input the message from
//! [VoprfClient::blind] (a [BlindedElement]), and runs //! [VoprfClient::blind] (a [BlindedElement]), and runs
//! [VoprfServer::evaluate] to produce a [VoprfServerEvaluateResult], //! [VoprfServer::blind_evaluate] to produce a [VoprfServerEvaluateResult],
//! which consists of an [EvaluationElement] to be sent to the client along with //! which consists of an [EvaluationElement] to be sent to the client along with
//! a proof. //! a proof.
//! //!
@@ -226,15 +261,15 @@
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerEvaluateResult { message, proof } = //! let VoprfServerEvaluateResult { message, proof } =
//! server.evaluate(&mut server_rng, &client_blind_result.message); //! server.blind_evaluate(&mut server_rng, &client_blind_result.message);
//! ``` //! ```
//! //!
//! ### Client Finalization //! ### Client Finalization
//! //!
//! In the final step, the client takes as input the message from //! In the final step, the client takes as input the message from
//! [VoprfServer::evaluate] (an [EvaluationElement]), the proof, and the //! [VoprfServer::blind_evaluate] (an [EvaluationElement]), the proof, and the
//! server's public key, and runs [VoprfClient::finalize] to produce an //! server's public key, and runs [VoprfClient::finalize] to produce an output
//! output for the protocol. //! for the protocol.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "ristretto255")] //! # #[cfg(feature = "ristretto255")]
@@ -252,7 +287,7 @@
//! # use voprf::VoprfServer; //! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let server_evaluate_result = server.evaluate( //! # let server_evaluate_result = server.blind_evaluate(
//! # &mut server_rng, //! # &mut server_rng,
//! # &client_blind_result.message, //! # &client_blind_result.message,
//! # ); //! # );
@@ -269,6 +304,51 @@
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec()); //! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ``` //! ```
//! //!
//! ### Server Evaluation
//!
//! Optionally, if the server has direct access to the PRF input, then it need
//! not perform the oblivious computation and can simply run
//! [VoprfServer::evaluate] to generate an output which matches the output
//! produced by an execution of the oblivious protocol on the same input and
//! key.
//!
//! ```
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let server_evaluate_result = server.blind_evaluate(
//! # &mut server_rng,
//! # &client_blind_result.message,
//! # );
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(
//! b"input",
//! &server_evaluate_result.message,
//! &server_evaluate_result.proof,
//! server.get_public_key(),
//! )
//! .expect("Unable to perform client finalization");
//!
//! let server_evaluate_result = server
//! .evaluate(b"input")
//! .expect("Unable to perform the server evaluation");
//!
//! assert_eq!(client_finalize_result, server_evaluate_result);
//! ```
//!
//! # Advanced Usage //! # Advanced Usage
//! //!
//! There are two additional (and optional) extensions to the core VOPRF //! There are two additional (and optional) extensions to the core VOPRF
@@ -279,9 +359,9 @@
//! //!
//! It is sometimes desirable to generate only a single, constant-size proof for //! It is sometimes desirable to generate only a single, constant-size proof for
//! an unbounded number of VOPRF evaluations (on arbitrary inputs). //! an unbounded number of VOPRF evaluations (on arbitrary inputs).
//! [VoprfClient] and [VoprfServer] support a batch API for handling //! [VoprfClient] and [VoprfServer] support a batch API for handling this case.
//! this case. In the following example, we show how to use the batch API to //! In the following example, we show how to use the batch API to produce a
//! produce a single proof for 10 parallel VOPRF evaluations. //! single proof for 10 parallel VOPRF evaluations.
//! //!
//! First, the client produces 10 blindings, storing their resulting states and //! First, the client produces 10 blindings, storing their resulting states and
//! messages: //! messages:
@@ -305,8 +385,8 @@
//! } //! }
//! ``` //! ```
//! //!
//! Next, the server calls the [VoprfServer::batch_evaluate_prepare] and //! Next, the server calls the [VoprfServer::batch_blind_evaluate_prepare] and
//! [VoprfServer::batch_evaluate_finish] function on a set of client //! [VoprfServer::batch_blind_evaluate_finish] function on a set of client
//! messages, to produce a corresponding set of messages to be returned to the //! messages, to produce a corresponding set of messages to be returned to the
//! client (returned in the same order), along with a single proof: //! client (returned in the same order), along with a single proof:
//! //!
@@ -332,15 +412,15 @@
//! # use voprf::VoprfServer; //! # use voprf::VoprfServer;
//! let mut server_rng = OsRng; //! let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let prepared_evaluation_elements = server.batch_evaluate_prepare(client_messages.iter()); //! let prepared_evaluation_elements = server.batch_blind_evaluate_prepare(client_messages.iter());
//! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect(); //! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
//! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server //! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
//! .batch_evaluate_finish(&mut server_rng, client_messages.iter(), &prepared_elements) //! .batch_blind_evaluate_finish(&mut server_rng, client_messages.iter(), &prepared_elements)
//! .expect("Unable to perform server batch evaluate"); //! .expect("Unable to perform server batch evaluate");
//! let messages: Vec<_> = messages.collect(); //! let messages: Vec<_> = messages.collect();
//! ``` //! ```
//! //!
//! If `alloc` is available, `VoprfServer::batch_evaluate` can be called //! If `alloc` is available, `VoprfServer::batch_blind_evaluate` can be called
//! to avoid having to collect output manually: //! to avoid having to collect output manually:
//! //!
//! ``` //! ```
@@ -367,15 +447,15 @@
//! let mut server_rng = OsRng; //! let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerBatchEvaluateResult { messages, proof } = server //! let VoprfServerBatchEvaluateResult { messages, proof } = server
//! .batch_evaluate(&mut server_rng, &client_messages) //! .batch_blind_evaluate(&mut server_rng, &client_messages)
//! .expect("Unable to perform server batch evaluate"); //! .expect("Unable to perform server batch evaluate");
//! # } //! # }
//! ``` //! ```
//! //!
//! Then, the client calls [VoprfClient::batch_finalize] on the client //! Then, the client calls [VoprfClient::batch_finalize] on the client states
//! states saved from the first step, along with the messages returned by the //! saved from the first step, along with the messages returned by the server,
//! server, along with the server's proof, in order to produce a vector of //! along with the server's proof, in order to produce a vector of outputs if
//! outputs if the proof verifies correctly. //! the proof verifies correctly.
//! //!
//! ``` //! ```
//! # #[cfg(feature = "alloc")] { //! # #[cfg(feature = "alloc")] {
@@ -401,7 +481,7 @@
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap(); //! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let VoprfServerBatchEvaluateResult { messages, proof } = server //! # let VoprfServerBatchEvaluateResult { messages, proof } = server
//! # .batch_evaluate(&mut server_rng, &client_messages) //! # .batch_blind_evaluate(&mut server_rng, &client_messages)
//! # .expect("Unable to perform server batch evaluate"); //! # .expect("Unable to perform server batch evaluate");
//! let client_batch_finalize_result = VoprfClient::batch_finalize( //! let client_batch_finalize_result = VoprfClient::batch_finalize(
//! &[b"input"; 10], //! &[b"input"; 10],
@@ -420,17 +500,17 @@
//! ## Metadata //! ## Metadata
//! //!
//! The optional metadata parameter included in the POPRF mode allows clients //! The optional metadata parameter included in the POPRF mode allows clients
//! and servers to cryptographically bind additional data to the //! and servers to cryptographically bind additional data to the VOPRF output.
//! VOPRF output. This metadata is known to both parties at the start of the //! This metadata is known to both parties at the start of the protocol, and is
//! protocol, and is inserted under the server's evaluate step and the client's //! inserted under the server's blind evaluate step and the client's finalize
//! finalize step. This metadata can be constructed with some type of //! step. This metadata can be constructed with some type of higher-level domain
//! higher-level domain separation to avoid cross-protocol attacks or related //! separation to avoid cross-protocol attacks or related issues.
//! issues.
//! //!
//! The API for POPRF mode is similar to VOPRF mode, except that a [PoprfServer] //! The API for POPRF mode is similar to VOPRF mode, except that a [PoprfServer]
//! and [PoprfClient] are used, and that each of the functions accept an //! and [PoprfClient] are used, and that each of the functions accept an
//! additional (and optional) info parameter which represents the public input. //! additional (and optional) info parameter which represents the public input.
//! See <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#name-poprf-public-input> //! See
//! <https://www.rfc-editor.org/rfc/rfc9497#name-poprf-public-input>
//! for more detailed information on how this public input should be used. //! for more detailed information on how this public input should be used.
//! //!
//! # Features //! # Features
@@ -451,23 +531,15 @@
//! a [`CipherSuite`]. //! a [`CipherSuite`].
//! //!
//! - The `ristretto255` feature enables using [`Ristretto255`] as the //! - The `ristretto255` feature enables using [`Ristretto255`] as the
//! underlying group for the [Group] choice. A backend feature, which are //! underlying group for the [Group] choice. To select a specific backend see
//! re-exported from [curve25519-dalek] and allow for selecting the //! the [curve25519-dalek] documentation.
//! corresponding backend for the curve arithmetic used, has to be selected,
//! otherwise compilation will fail. The `ristretto255-u64` feature is
//! included as the default. Other features are mapped as `ristretto255-u32`,
//! `ristretto255-fiat-u64` and `ristretto255-fiat-u32`. Any `ristretto255-*`
//! backend feature will enable the `ristretto255` feature.
//! //!
//! - The `ristretto255-simd` feature is re-exported from [curve25519-dalek] and //! [curve25519-dalek]:
//! enables parallel formulas, using either AVX2 or AVX512-IFMA. This will //! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends)
//! automatically enable the `ristretto255-u64` feature and requires Rust
//! nightly.
//!
//! [curve25519-dalek]: (https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features)
#![cfg_attr(not(test), deny(unsafe_code))]
#![no_std] #![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(test), deny(unsafe_code))]
#![warn( #![warn(
clippy::cargo, clippy::cargo,
clippy::missing_errors_doc, clippy::missing_errors_doc,
@@ -482,9 +554,6 @@ extern crate alloc;
#[cfg(feature = "std")] #[cfg(feature = "std")]
extern crate std; extern crate std;
#[cfg(feature = "serde")]
extern crate serde_ as serde;
mod ciphersuite; mod ciphersuite;
mod common; mod common;
mod error; mod error;
@@ -500,6 +569,8 @@ mod tests;
// Exports // Exports
pub use crate::ciphersuite::CipherSuite; pub use crate::ciphersuite::CipherSuite;
#[cfg(feature = "danger")]
pub use crate::common::derive_key;
pub use crate::common::{ pub use crate::common::{
BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof,
}; };
+81 -20
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Contains the main OPRF API //! Contains the main OPRF API
@@ -17,8 +18,8 @@ use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use crate::common::{ use crate::common::{
derive_key, deterministic_blind_unchecked, i2osp_2, BlindedElement, EvaluationElement, Mode, derive_key_internal, deterministic_blind_unchecked, hash_to_group, i2osp_2,
STR_FINALIZE, server_evaluate_hash_input, BlindedElement, EvaluationElement, Mode, STR_FINALIZE,
}; };
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::serialization::serde::Scalar; use crate::serialization::serde::Scalar;
@@ -41,7 +42,7 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct OprfClient<CS: CipherSuite> pub struct OprfClient<CS: CipherSuite>
where where
@@ -59,7 +60,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct OprfServer<CS: CipherSuite> pub struct OprfServer<CS: CipherSuite>
where where
@@ -189,7 +190,7 @@ where
/// then `u16::MAX - 3`. /// then `u16::MAX - 3`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed. /// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> { pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
let sk = derive_key::<CS>(seed, info, Mode::Oprf)?; let sk = derive_key_internal::<CS>(seed, info, Mode::Oprf)?;
Ok(Self { sk }) Ok(Self { sk })
} }
@@ -202,9 +203,25 @@ where
/// Computes the second step for the multiplicative blinding version of /// Computes the second step for the multiplicative blinding version of
/// DH-OPRF. This message is sent from the server (who holds the OPRF key) /// DH-OPRF. This message is sent from the server (who holds the OPRF key)
/// to the client. /// to the client.
pub fn evaluate(&self, blinded_element: &BlindedElement<CS>) -> EvaluationElement<CS> { pub fn blind_evaluate(&self, blinded_element: &BlindedElement<CS>) -> EvaluationElement<CS> {
EvaluationElement(blinded_element.0 * &self.sk) EvaluationElement(blinded_element.0 * &self.sk)
} }
/// Computes the output of the OPRF on the server side
///
/// # Errors
/// [`Error::Input`] if the `input` is longer then [`u16::MAX`].
pub fn evaluate(&self, input: &[u8]) -> Result<Output<<CS as CipherSuite>::Hash>> {
let input_element = hash_to_group::<CS>(input, Mode::Oprf)?;
if CS::Group::is_identity_elem(input_element).into() {
return Err(Error::Input);
};
let evaluated_element = input_element * &self.sk;
let issued_element = CS::Group::serialize_elem(evaluated_element);
server_evaluate_hash_input::<CS>(input, None, issued_element)
}
} }
///////////////////////// /////////////////////////
@@ -261,7 +278,7 @@ where
.chain_update(input.as_ref()) .chain_update(input.as_ref())
.chain_update(elem_len) .chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(unblinded_element)) .chain_update(CS::Group::serialize_elem(unblinded_element))
.chain_update(&STR_FINALIZE) .chain_update(STR_FINALIZE)
.finalize()) .finalize())
}) })
} }
@@ -275,11 +292,10 @@ where
mod tests { mod tests {
use core::ptr; use core::ptr;
use generic_array::sequence::Concat;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use super::*; use super::*;
use crate::common::{create_context_string, STR_HASH_TO_GROUP}; use crate::common::{Dst, STR_HASH_TO_GROUP};
use crate::Group; use crate::Group;
fn prf<CS: CipherSuite>( fn prf<CS: CipherSuite>(
@@ -292,8 +308,8 @@ mod tests {
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap(); let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
let res = point * &key; let res = point * &key;
@@ -312,7 +328,7 @@ mod tests {
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap(); let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap(); let server = OprfServer::<CS>::new(&mut rng).unwrap();
let message = server.evaluate(&client_blind_result.message); let message = server.blind_evaluate(&client_blind_result.message);
let client_finalize_result = client_blind_result.state.finalize(input, &message).unwrap(); let client_finalize_result = client_blind_result.state.finalize(input, &message).unwrap();
let res2 = prf::<CS>(input, server.get_private_key(), &[], Mode::Oprf); let res2 = prf::<CS>(input, server.get_private_key(), &[], Mode::Oprf);
assert_eq!(client_finalize_result, res2); assert_eq!(client_finalize_result, res2);
@@ -332,9 +348,8 @@ mod tests {
.finalize(&input, &EvaluationElement(client_blind_result.message.0)) .finalize(&input, &EvaluationElement(client_blind_result.message.0))
.unwrap(); .unwrap();
let dst = let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(Mode::Oprf)); let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst.as_dst()).unwrap();
let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst).unwrap();
let res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[]) let res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[])
.next() .next()
.unwrap() .unwrap()
@@ -343,6 +358,34 @@ mod tests {
assert_eq!(client_finalize_result, res2); assert_eq!(client_finalize_result, res2);
} }
fn server_evaluate<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.blind_evaluate(&client_blind_result.message);
let client_finalize = client_blind_result
.state
.finalize(input, &server_result)
.unwrap();
// We expect the outputs from client and server to be equal given an identical
// input
let server_evaluate = server.evaluate(input).unwrap();
assert_eq!(client_finalize, server_evaluate);
// We expect the outputs from client and server to be different given different
// inputs
let wrong_input = b"wrong input";
let server_evaluate = server.evaluate(wrong_input).unwrap();
assert!(client_finalize != server_evaluate);
}
fn zeroize_oprf_client<CS: CipherSuite>() fn zeroize_oprf_client<CS: CipherSuite>()
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
@@ -370,7 +413,7 @@ mod tests {
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap(); let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap(); let server = OprfServer::<CS>::new(&mut rng).unwrap();
let mut message = server.evaluate(&client_blind_result.message); let mut message = server.blind_evaluate(&client_blind_result.message);
let mut state = server; let mut state = server;
unsafe { ptr::drop_in_place(&mut state) }; unsafe { ptr::drop_in_place(&mut state) };
@@ -383,6 +426,8 @@ mod tests {
#[test] #[test]
fn test_functionality() -> Result<()> { fn test_functionality() -> Result<()> {
use p256::NistP256; use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")] #[cfg(feature = "ristretto255")]
{ {
@@ -390,6 +435,7 @@ mod tests {
base_retrieval::<Ristretto255>(); base_retrieval::<Ristretto255>();
base_inversion_unsalted::<Ristretto255>(); base_inversion_unsalted::<Ristretto255>();
server_evaluate::<Ristretto255>();
zeroize_oprf_client::<Ristretto255>(); zeroize_oprf_client::<Ristretto255>();
zeroize_oprf_server::<Ristretto255>(); zeroize_oprf_server::<Ristretto255>();
@@ -397,10 +443,25 @@ mod tests {
base_retrieval::<NistP256>(); base_retrieval::<NistP256>();
base_inversion_unsalted::<NistP256>(); base_inversion_unsalted::<NistP256>();
server_evaluate::<NistP256>();
zeroize_oprf_client::<NistP256>(); zeroize_oprf_client::<NistP256>();
zeroize_oprf_server::<NistP256>(); zeroize_oprf_server::<NistP256>();
base_retrieval::<NistP384>();
base_inversion_unsalted::<NistP384>();
server_evaluate::<NistP384>();
zeroize_oprf_client::<NistP384>();
zeroize_oprf_server::<NistP384>();
base_retrieval::<NistP521>();
base_inversion_unsalted::<NistP521>();
server_evaluate::<NistP521>();
zeroize_oprf_client::<NistP521>();
zeroize_oprf_server::<NistP521>();
Ok(()) Ok(())
} }
} }
+118 -43
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Contains the main POPRF API //! Contains the main POPRF API
@@ -14,15 +15,14 @@ use core::iter::{self, Map, Repeat, Zip};
use derive_where::derive_where; use derive_where::derive_where;
use digest::core_api::BlockSizeUser; use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser}; use digest::{Digest, Output, OutputSizeUser};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
use generic_array::GenericArray; use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use crate::common::{ use crate::common::{
create_context_string, derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2, derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2,
verify_proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode,
STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO, PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO,
}; };
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar}; use crate::serialization::serde::{Element, Scalar};
@@ -40,7 +40,7 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct PoprfClient<CS: CipherSuite> pub struct PoprfClient<CS: CipherSuite>
where where
@@ -60,7 +60,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct PoprfServer<CS: CipherSuite> pub struct PoprfServer<CS: CipherSuite>
where where
@@ -89,8 +89,8 @@ where
/// # Errors /// # Errors
/// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
pub fn blind<R: RngCore + CryptoRng>( pub fn blind<R: RngCore + CryptoRng>(
blinding_factor_rng: &mut R,
input: &[u8], input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<PoprfClientBlindResult<CS>> { ) -> Result<PoprfClientBlindResult<CS>> {
let blind = CS::Group::random_scalar(blinding_factor_rng); let blind = CS::Group::random_scalar(blinding_factor_rng);
Self::deterministic_blind_unchecked_inner(input, blind) Self::deterministic_blind_unchecked_inner(input, blind)
@@ -248,7 +248,7 @@ where
/// # Errors /// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed. /// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn evaluate<R: RngCore + CryptoRng>( pub fn blind_evaluate<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
blinded_element: &BlindedElement<CS>, blinded_element: &BlindedElement<CS>,
@@ -257,7 +257,7 @@ where
let PoprfServerBatchEvaluatePrepareResult { let PoprfServerBatchEvaluatePrepareResult {
mut prepared_evaluation_elements, mut prepared_evaluation_elements,
prepared_tweak, prepared_tweak,
} = self.batch_evaluate_prepare(iter::once(blinded_element), info)?; } = self.batch_blind_evaluate_prepare(iter::once(blinded_element), info)?;
let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap(); let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap();
let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element); let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element);
@@ -265,7 +265,7 @@ where
let PoprfServerBatchEvaluateFinishResult { let PoprfServerBatchEvaluateFinishResult {
mut messages, mut messages,
proof, proof,
} = Self::batch_evaluate_finish( } = Self::batch_blind_evaluate_finish(
rng, rng,
iter::once(blinded_element), iter::once(blinded_element),
prepared_evaluation_elements, prepared_evaluation_elements,
@@ -286,7 +286,7 @@ where
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed. /// - [`Error::Protocol`] if the protocol fails and can't be completed.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn batch_evaluate<'a, R: RngCore + CryptoRng, IE>( pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, IE>(
&self, &self,
rng: &mut R, rng: &mut R,
blinded_elements: &'a IE, blinded_elements: &'a IE,
@@ -300,13 +300,13 @@ where
let PoprfServerBatchEvaluatePrepareResult { let PoprfServerBatchEvaluatePrepareResult {
prepared_evaluation_elements, prepared_evaluation_elements,
prepared_tweak, prepared_tweak,
} = self.batch_evaluate_prepare(blinded_elements.into_iter(), info)?; } = self.batch_blind_evaluate_prepare(blinded_elements.into_iter(), info)?;
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect(); let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
// This can't fail because we know the size of the inputs. // This can't fail because we know the size of the inputs.
let PoprfServerBatchEvaluateFinishResult { messages, proof } = let PoprfServerBatchEvaluateFinishResult { messages, proof } =
Self::batch_evaluate_finish::<_, _, Vec<_>>( Self::batch_blind_evaluate_finish::<_, _, Vec<_>>(
rng, rng,
blinded_elements.into_iter(), blinded_elements.into_iter(),
&prepared_evaluation_elements, &prepared_evaluation_elements,
@@ -319,15 +319,15 @@ where
Ok(PoprfServerBatchEvaluateResult { messages, proof }) Ok(PoprfServerBatchEvaluateResult { messages, proof })
} }
/// Alternative version of `batch_evaluate` without /// Alternative version of `batch_blind_evaluate` without
/// memory allocation. Returned [`PreparedEvaluationElement`] have to /// memory allocation. Returned [`PreparedEvaluationElement`] have to
/// be [`collect`](Iterator::collect)ed and passed into /// be [`collect`](Iterator::collect)ed and passed into
/// [`batch_evaluate_finish`](Self::batch_evaluate_finish). /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish).
/// ///
/// # Errors /// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed. /// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn batch_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>( pub fn batch_blind_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>(
&self, &self,
blinded_elements: I, blinded_elements: I,
info: Option<&[u8]>, info: Option<&[u8]>,
@@ -349,14 +349,14 @@ where
}) })
} }
/// See [`batch_evaluate_prepare`](Self::batch_evaluate_prepare) for more /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare)
/// details. /// for more details.
/// ///
/// # Errors /// # Errors
/// [`Error::Batch`] if the number of `blinded_elements` and /// [`Error::Batch`] if the number of `blinded_elements` and
/// `prepared_evaluation_elements` don't match or is longer then /// `prepared_evaluation_elements` don't match or is longer then
/// [`u16::MAX`] /// [`u16::MAX`]
pub fn batch_evaluate_finish< pub fn batch_blind_evaluate_finish<
'a, 'a,
'b, 'b,
R: RngCore + CryptoRng, R: RngCore + CryptoRng,
@@ -398,6 +398,29 @@ where
Ok(PoprfServerBatchEvaluateFinishResult { messages, proof }) Ok(PoprfServerBatchEvaluateFinishResult { messages, proof })
} }
/// Computes the output of the VOPRF on the server side
///
/// # Errors
/// [`Error::Input`] if the `input` is longer then [`u16::MAX`].
pub fn evaluate(
&self,
input: &[u8],
info: Option<&[u8]>,
) -> Result<Output<<CS as CipherSuite>::Hash>> {
let input_element = hash_to_group::<CS>(input, Mode::Poprf)?;
if CS::Group::is_identity_elem(input_element).into() {
return Err(Error::Input);
};
let tweak = compute_tweak::<CS>(self.sk, info)?;
let evaluated_element = input_element * &CS::Group::invert_scalar(tweak);
let issued_element = CS::Group::serialize_elem(evaluated_element);
server_evaluate_hash_input::<CS>(input, info, issued_element)
}
/// Retrieves the server's public key /// Retrieves the server's public key
pub fn get_public_key(&self) -> <CS::Group as Group>::Elem { pub fn get_public_key(&self) -> <CS::Group as Group>::Elem {
self.pk self.pk
@@ -517,7 +540,7 @@ pub type PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct PoprfPreparedTweak<CS: CipherSuite>( pub struct PoprfPreparedTweak<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))] #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
@@ -592,10 +615,9 @@ where
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
let framed_info = [STR_INFO.as_slice(), &info_len, info]; let framed_info = [STR_INFO.as_slice(), &info_len, info];
let dst = let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
// This can't fail, the size of the `input` is known. // This can't fail, the size of the `input` is known.
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst).unwrap(); let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_dst()).unwrap();
let t = CS::Group::base_elem() * &m; let t = CS::Group::base_elem() * &m;
let tweaked_key = t + &pk; let tweaked_key = t + &pk;
@@ -630,10 +652,9 @@ where
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
let framed_info = [STR_INFO.as_slice(), &info_len, info]; let framed_info = [STR_INFO.as_slice(), &info_len, info];
let dst = let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
// This can't fail, the size of the `input` is known. // This can't fail, the size of the `input` is known.
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst).unwrap(); let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_dst()).unwrap();
let t = sk + &m; let t = sk + &m;
@@ -699,7 +720,7 @@ where
)?; )?;
Ok(blinds Ok(blinds
.zip(messages.into_iter()) .zip(messages)
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
} }
@@ -786,8 +807,8 @@ mod tests {
{ {
let t = compute_tweak::<CS>(key, Some(info)).unwrap(); let t = compute_tweak::<CS>(key, Some(info)).unwrap();
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap(); let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
// evaluatedElement = G.ScalarInverse(t) * blindedElement // evaluatedElement = G.ScalarInverse(t) * blindedElement
let res = point * &CS::Group::invert_scalar(t); let res = point * &CS::Group::invert_scalar(t);
@@ -808,9 +829,9 @@ mod tests {
let info = b"info"; let info = b"info";
let mut rng = OsRng; let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap(); let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap(); let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server_result = server let server_result = server
.evaluate(&mut rng, &client_blind_result.message, Some(info)) .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap(); .unwrap();
let client_finalize_result = client_blind_result let client_finalize_result = client_blind_result
.state .state
@@ -835,15 +856,14 @@ mod tests {
let info = b"info"; let info = b"info";
let mut rng = OsRng; let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap(); let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap(); let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server_result = server let server_result = server
.evaluate(&mut rng, &client_blind_result.message, Some(info)) .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap(); .unwrap();
let wrong_pk = { let wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP) let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
.concat(create_context_string::<CS>(Mode::Oprf));
// Choose a group element that is unlikely to be the right public key // Choose a group element that is unlikely to be the right public key
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst).unwrap() CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst.as_dst()).unwrap()
}; };
let client_finalize_result = client_blind_result.state.finalize( let client_finalize_result = client_blind_result.state.finalize(
input, input,
@@ -855,6 +875,43 @@ mod tests {
assert!(client_finalize_result.is_err()); assert!(client_finalize_result.is_err());
} }
fn verifiable_server_evaluate<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let info = Some(b"info".as_slice());
let mut rng = OsRng;
let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server
.blind_evaluate(&mut rng, &client_blind_result.message, info)
.unwrap();
let client_finalize = client_blind_result
.state
.finalize(
input,
&server_result.message,
&server_result.proof,
server.get_public_key(),
info,
)
.unwrap();
// We expect the outputs from client and server to be equal given an identical
// input
let server_evaluate = server.evaluate(input, info).unwrap();
assert_eq!(client_finalize, server_evaluate);
// We expect the outputs from client and server to be different given different
// inputs
let wrong_input = b"wrong input";
let server_evaluate = server.evaluate(wrong_input, info).unwrap();
assert!(client_finalize != server_evaluate);
}
fn zeroize_verifiable_client<CS: CipherSuite>() fn zeroize_verifiable_client<CS: CipherSuite>()
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
@@ -864,7 +921,7 @@ mod tests {
{ {
let input = b"input"; let input = b"input";
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap(); let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let mut state = client_blind_result.state; let mut state = client_blind_result.state;
unsafe { ptr::drop_in_place(&mut state) }; unsafe { ptr::drop_in_place(&mut state) };
@@ -888,9 +945,9 @@ mod tests {
let info = b"info"; let info = b"info";
let mut rng = OsRng; let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap(); let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap(); let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server_result = server let server_result = server
.evaluate(&mut rng, &client_blind_result.message, Some(info)) .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap(); .unwrap();
let mut state = server; let mut state = server;
@@ -909,6 +966,8 @@ mod tests {
#[test] #[test]
fn test_functionality() -> Result<()> { fn test_functionality() -> Result<()> {
use p256::NistP256; use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")] #[cfg(feature = "ristretto255")]
{ {
@@ -916,6 +975,7 @@ mod tests {
verifiable_retrieval::<Ristretto255>(); verifiable_retrieval::<Ristretto255>();
verifiable_bad_public_key::<Ristretto255>(); verifiable_bad_public_key::<Ristretto255>();
verifiable_server_evaluate::<Ristretto255>();
zeroize_verifiable_client::<Ristretto255>(); zeroize_verifiable_client::<Ristretto255>();
zeroize_verifiable_server::<Ristretto255>(); zeroize_verifiable_server::<Ristretto255>();
@@ -923,10 +983,25 @@ mod tests {
verifiable_retrieval::<NistP256>(); verifiable_retrieval::<NistP256>();
verifiable_bad_public_key::<NistP256>(); verifiable_bad_public_key::<NistP256>();
verifiable_server_evaluate::<NistP256>();
zeroize_verifiable_client::<NistP256>(); zeroize_verifiable_client::<NistP256>();
zeroize_verifiable_server::<NistP256>(); zeroize_verifiable_server::<NistP256>();
verifiable_retrieval::<NistP384>();
verifiable_bad_public_key::<NistP384>();
verifiable_server_evaluate::<NistP384>();
zeroize_verifiable_client::<NistP384>();
zeroize_verifiable_server::<NistP384>();
verifiable_retrieval::<NistP521>();
verifiable_bad_public_key::<NistP521>();
verifiable_server_evaluate::<NistP521>();
zeroize_verifiable_client::<NistP521>();
zeroize_verifiable_server::<NistP521>();
Ok(()) Ok(())
} }
} }
+9 -6
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Handles the serialization of each of the components used in the VOPRF //! Handles the serialization of each of the components used in the VOPRF
//! protocol //! protocol
@@ -306,11 +307,11 @@ fn deserialize_scalar<G: Group>(input: &mut &[u8]) -> Result<G::Scalar> {
} }
trait SliceExt { trait SliceExt {
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self>; fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self>;
} }
impl<T> SliceExt for [T] { impl<T> SliceExt for [T] {
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self> { fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self> {
if take > self.len() { if take > self.len() {
return None; return None;
} }
@@ -389,6 +390,8 @@ mod test {
} }
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]); let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
let _ = $item::<p384::NistP384>::deserialize(&$bytes[..]);
let _ = $item::<p521::NistP521>::deserialize(&$bytes[..]);
}; };
} }
+915 -1027
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp::min; use core::cmp::min;
@@ -50,7 +51,7 @@ impl RngCore for CycleRng {
#[inline] #[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) { fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len()); let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]); dest[..len].copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len); rotate_left(&mut self.v, len);
} }
+5 -4
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
mod cfrg_vectors; mod cfrg_vectors;
mod mock_rng; mod mock_rng;
+9 -8
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::vec::Vec; use alloc::vec::Vec;
@@ -14,7 +15,7 @@ pub(crate) fn rfc_to_json(input: &str) -> String {
} }
fn parse_ciphersuites(input: &str) -> String { fn parse_ciphersuites(input: &str) -> String {
let re = regex::Regex::new(r"## OPRF\((?P<ciphersuite>.+?)\)").unwrap(); let re = regex::Regex::new(r"\nA\.\d\. (?P<ciphersuite>.+?)\n\n").unwrap();
let mut ciphersuites = vec![]; let mut ciphersuites = vec![];
let chunks: Vec<&str> = re.split(input).collect(); let chunks: Vec<&str> = re.split(input).collect();
@@ -33,7 +34,7 @@ fn parse_ciphersuites(input: &str) -> String {
} }
fn parse_modes(input: &str) -> String { fn parse_modes(input: &str) -> String {
let re = regex::Regex::new(r"### (?P<mode>.*+) Mode").unwrap(); let re = regex::Regex::new(r"A\.\d.\d\. (?P<mode>.*?) Mode").unwrap();
let mut modes = vec![]; let mut modes = vec![];
let chunks: Vec<&str> = re.split(input).collect(); let chunks: Vec<&str> = re.split(input).collect();
@@ -52,7 +53,7 @@ fn parse_modes(input: &str) -> String {
} }
fn parse_vectors(input: &str) -> String { fn parse_vectors(input: &str) -> String {
let re = regex::Regex::new(r"Test Vector.*+\n").unwrap(); let re = regex::Regex::new(r"A\.\d.\d\.\d\. Test Vector.*+\n").unwrap();
let mut vectors = vec![]; let mut vectors = vec![];
let chunks: Vec<&str> = re.split(input).collect(); let chunks: Vec<&str> = re.split(input).collect();
@@ -96,7 +97,7 @@ fn parse_params(input: &str) -> String {
let key = iter.next().unwrap().split_whitespace().next().unwrap(); let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap(); let val = iter.next().unwrap().split_whitespace().next().unwrap();
param = format!(" \"{}\": \"{}", key, val); param = format!(" \"{key}\": \"{val}");
} else { } else {
let s = line.trim().to_string(); let s = line.trim().to_string();
if s.contains('~') || s.contains('#') { if s.contains('~') || s.contains('#') {
+154 -35
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
use alloc::string::String; use alloc::string::String;
use alloc::vec; use alloc::vec;
@@ -14,7 +15,7 @@ use digest::core_api::BlockSizeUser;
use digest::OutputSizeUser; use digest::OutputSizeUser;
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256};
use generic_array::ArrayLength; use generic_array::ArrayLength;
use json::JsonValue; use serde_json::Value;
use crate::tests::mock_rng::CycleRng; use crate::tests::mock_rng::CycleRng;
use crate::tests::parser::*; use crate::tests::parser::*;
@@ -40,7 +41,7 @@ struct VOPRFTestVectorParameters {
output: Vec<Vec<u8>>, output: Vec<Vec<u8>>,
} }
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters { fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters { VOPRFTestVectorParameters {
seed: decode(values, "Seed"), seed: decode(values, "Seed"),
sksm: decode(values, "skSm"), sksm: decode(values, "skSm"),
@@ -57,18 +58,18 @@ fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
} }
} }
fn decode(values: &JsonValue, key: &str) -> Vec<u8> { fn decode(values: &Value, key: &str) -> Vec<u8> {
values[key] values[key]
.as_str() .as_str()
.and_then(|s| hex::decode(&s).ok()) .and_then(|s| hex::decode(s).ok())
.unwrap_or_default() .unwrap_or_default()
} }
fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> { fn decode_vec(values: &Value, key: &str) -> Vec<Vec<u8>> {
let s = values[key].as_str().unwrap(); let s = values[key].as_str().unwrap();
let res = match s.contains(',') { let res = match s.contains(',') {
true => Some(s.split(',').map(|x| hex::decode(&x).unwrap()).collect()), true => Some(s.split(',').map(|x| hex::decode(x).unwrap()).collect()),
false => Some(vec![hex::decode(&s).unwrap()]), false => Some(vec![hex::decode(s).unwrap()]),
}; };
res.unwrap() res.unwrap()
} }
@@ -76,8 +77,10 @@ fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> {
macro_rules! json_to_test_vectors { macro_rules! json_to_test_vectors {
( $v:ident, $cs:expr, $mode:expr ) => { ( $v:ident, $cs:expr, $mode:expr ) => {
$v[$cs][$mode] $v[$cs][$mode]
.members() .as_array()
.map(|x| populate_test_vectors(&x)) .into_iter()
.flatten()
.map(populate_test_vectors)
.collect::<Vec<VOPRFTestVectorParameters>>() .collect::<Vec<VOPRFTestVectorParameters>>()
}; };
} }
@@ -85,8 +88,10 @@ macro_rules! json_to_test_vectors {
#[test] #[test]
fn test_vectors() -> Result<()> { fn test_vectors() -> Result<()> {
use p256::NistP256; use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
let rfc = json::parse(rfc_to_json(super::cfrg_vectors::VECTORS).as_str()) let rfc: Value = serde_json::from_str(rfc_to_json(super::cfrg_vectors::VECTORS).as_str())
.expect("Could not parse json"); .expect("Could not parse json");
#[cfg(feature = "ristretto255")] #[cfg(feature = "ristretto255")]
@@ -95,61 +100,121 @@ fn test_vectors() -> Result<()> {
let ristretto_oprf_tvs = json_to_test_vectors!( let ristretto_oprf_tvs = json_to_test_vectors!(
rfc, rfc,
String::from("ristretto255, SHA-512"), String::from("ristretto255-SHA512"),
String::from("OPRF") String::from("OPRF")
); );
assert_ne!(ristretto_oprf_tvs.len(), 0); assert_ne!(ristretto_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<Ristretto255>(&ristretto_oprf_tvs)?; test_oprf_seed_to_key::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_blind::<Ristretto255>(&ristretto_oprf_tvs)?; test_oprf_blind::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?; test_oprf_blind_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_finalize::<Ristretto255>(&ristretto_oprf_tvs)?; test_oprf_finalize::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
let ristretto_voprf_tvs = json_to_test_vectors!( let ristretto_voprf_tvs = json_to_test_vectors!(
rfc, rfc,
String::from("ristretto255, SHA-512"), String::from("ristretto255-SHA512"),
String::from("VOPRF") String::from("VOPRF")
); );
assert_ne!(ristretto_voprf_tvs.len(), 0); assert_ne!(ristretto_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<Ristretto255>(&ristretto_voprf_tvs)?; test_voprf_seed_to_key::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_blind::<Ristretto255>(&ristretto_voprf_tvs)?; test_voprf_blind::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?; test_voprf_blind_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_finalize::<Ristretto255>(&ristretto_voprf_tvs)?; test_voprf_finalize::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
let ristretto_poprf_tvs = json_to_test_vectors!( let ristretto_poprf_tvs = json_to_test_vectors!(
rfc, rfc,
String::from("ristretto255, SHA-512"), String::from("ristretto255-SHA512"),
String::from("POPRF") String::from("POPRF")
); );
assert_ne!(ristretto_poprf_tvs.len(), 0); assert_ne!(ristretto_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<Ristretto255>(&ristretto_poprf_tvs)?; test_poprf_seed_to_key::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_blind::<Ristretto255>(&ristretto_poprf_tvs)?; test_poprf_blind::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?; test_poprf_blind_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_finalize::<Ristretto255>(&ristretto_poprf_tvs)?; test_poprf_finalize::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
} }
let p256_oprf_tvs = let p256_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("OPRF")); json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("OPRF"));
assert_ne!(p256_oprf_tvs.len(), 0); assert_ne!(p256_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP256>(&p256_oprf_tvs)?; test_oprf_seed_to_key::<NistP256>(&p256_oprf_tvs)?;
test_oprf_blind::<NistP256>(&p256_oprf_tvs)?; test_oprf_blind::<NistP256>(&p256_oprf_tvs)?;
test_oprf_evaluate::<NistP256>(&p256_oprf_tvs)?; test_oprf_blind_evaluate::<NistP256>(&p256_oprf_tvs)?;
test_oprf_finalize::<NistP256>(&p256_oprf_tvs)?; test_oprf_finalize::<NistP256>(&p256_oprf_tvs)?;
test_oprf_evaluate::<NistP256>(&p256_oprf_tvs)?;
let p256_voprf_tvs = let p256_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("VOPRF")); json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("VOPRF"));
assert_ne!(p256_voprf_tvs.len(), 0); assert_ne!(p256_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP256>(&p256_voprf_tvs)?; test_voprf_seed_to_key::<NistP256>(&p256_voprf_tvs)?;
test_voprf_blind::<NistP256>(&p256_voprf_tvs)?; test_voprf_blind::<NistP256>(&p256_voprf_tvs)?;
test_voprf_evaluate::<NistP256>(&p256_voprf_tvs)?; test_voprf_blind_evaluate::<NistP256>(&p256_voprf_tvs)?;
test_voprf_finalize::<NistP256>(&p256_voprf_tvs)?; test_voprf_finalize::<NistP256>(&p256_voprf_tvs)?;
test_voprf_evaluate::<NistP256>(&p256_voprf_tvs)?;
let p256_poprf_tvs = let p256_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("POPRF")); json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("POPRF"));
assert_ne!(p256_poprf_tvs.len(), 0); assert_ne!(p256_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP256>(&p256_poprf_tvs)?; test_poprf_seed_to_key::<NistP256>(&p256_poprf_tvs)?;
test_poprf_blind::<NistP256>(&p256_poprf_tvs)?; test_poprf_blind::<NistP256>(&p256_poprf_tvs)?;
test_poprf_evaluate::<NistP256>(&p256_poprf_tvs)?; test_poprf_blind_evaluate::<NistP256>(&p256_poprf_tvs)?;
test_poprf_finalize::<NistP256>(&p256_poprf_tvs)?; test_poprf_finalize::<NistP256>(&p256_poprf_tvs)?;
test_poprf_evaluate::<NistP256>(&p256_poprf_tvs)?;
let p384_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("OPRF"));
assert_ne!(p384_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP384>(&p384_oprf_tvs)?;
test_oprf_blind::<NistP384>(&p384_oprf_tvs)?;
test_oprf_blind_evaluate::<NistP384>(&p384_oprf_tvs)?;
test_oprf_finalize::<NistP384>(&p384_oprf_tvs)?;
test_oprf_evaluate::<NistP384>(&p384_oprf_tvs)?;
let p384_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("VOPRF"));
assert_ne!(p384_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP384>(&p384_voprf_tvs)?;
test_voprf_blind::<NistP384>(&p384_voprf_tvs)?;
test_voprf_blind_evaluate::<NistP384>(&p384_voprf_tvs)?;
test_voprf_finalize::<NistP384>(&p384_voprf_tvs)?;
test_voprf_evaluate::<NistP384>(&p384_voprf_tvs)?;
let p384_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("POPRF"));
assert_ne!(p384_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP384>(&p384_poprf_tvs)?;
test_poprf_blind::<NistP384>(&p384_poprf_tvs)?;
test_poprf_blind_evaluate::<NistP384>(&p384_poprf_tvs)?;
test_poprf_finalize::<NistP384>(&p384_poprf_tvs)?;
test_poprf_evaluate::<NistP384>(&p384_poprf_tvs)?;
let p521_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("OPRF"));
assert_ne!(p521_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP521>(&p521_oprf_tvs)?;
test_oprf_blind::<NistP521>(&p521_oprf_tvs)?;
test_oprf_blind_evaluate::<NistP521>(&p521_oprf_tvs)?;
test_oprf_finalize::<NistP521>(&p521_oprf_tvs)?;
test_oprf_evaluate::<NistP521>(&p521_oprf_tvs)?;
let p521_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("VOPRF"));
assert_ne!(p521_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP521>(&p521_voprf_tvs)?;
test_voprf_blind::<NistP521>(&p521_voprf_tvs)?;
test_voprf_blind_evaluate::<NistP521>(&p521_voprf_tvs)?;
test_voprf_finalize::<NistP521>(&p521_voprf_tvs)?;
test_voprf_evaluate::<NistP521>(&p521_voprf_tvs)?;
let p521_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("POPRF"));
assert_ne!(p521_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP521>(&p521_poprf_tvs)?;
test_poprf_blind::<NistP521>(&p521_poprf_tvs)?;
test_poprf_blind_evaluate::<NistP521>(&p521_poprf_tvs)?;
test_poprf_finalize::<NistP521>(&p521_poprf_tvs)?;
test_poprf_evaluate::<NistP521>(&p521_poprf_tvs)?;
Ok(()) Ok(())
} }
@@ -286,7 +351,7 @@ where
} }
// Tests sksm, blinded_element -> evaluation_element // Tests sksm, blinded_element -> evaluation_element
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> fn test_oprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
@@ -294,7 +359,7 @@ where
for parameters in tvs { for parameters in tvs {
for i in 0..parameters.input.len() { for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?; let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
let message = server.evaluate(&BlindedElement::deserialize( let message = server.blind_evaluate(&BlindedElement::deserialize(
&parameters.blinded_element[i], &parameters.blinded_element[i],
)?); )?);
@@ -307,7 +372,7 @@ where
Ok(()) Ok(())
} }
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> fn test_voprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
@@ -323,10 +388,11 @@ where
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?); blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
} }
let prepared_evaluation_elements = server.batch_evaluate_prepare(blinded_elements.iter()); let prepared_evaluation_elements =
server.batch_blind_evaluate_prepare(blinded_elements.iter());
let prepared_elements: Vec<_> = prepared_evaluation_elements.collect(); let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } = let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
server.batch_evaluate_finish(&mut rng, blinded_elements.iter(), &prepared_elements)?; .batch_blind_evaluate_finish(&mut rng, blinded_elements.iter(), &prepared_elements)?;
let messages: Vec<_> = messages.collect(); let messages: Vec<_> = messages.collect();
for (parameter, message) in parameters.evaluation_element.iter().zip(messages) { for (parameter, message) in parameters.evaluation_element.iter().zip(messages) {
@@ -338,7 +404,7 @@ where
Ok(()) Ok(())
} }
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> fn test_poprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
@@ -357,10 +423,10 @@ where
let PoprfServerBatchEvaluatePrepareResult { let PoprfServerBatchEvaluatePrepareResult {
prepared_evaluation_elements, prepared_evaluation_elements,
prepared_tweak, prepared_tweak,
} = server.batch_evaluate_prepare(blinded_elements.iter(), Some(&parameters.info))?; } = server.batch_blind_evaluate_prepare(blinded_elements.iter(), Some(&parameters.info))?;
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect(); let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
let PoprfServerBatchEvaluateFinishResult { messages, proof } = let PoprfServerBatchEvaluateFinishResult { messages, proof } =
PoprfServer::batch_evaluate_finish::<_, _, Vec<_>>( PoprfServer::batch_blind_evaluate_finish::<_, _, Vec<_>>(
&mut rng, &mut rng,
blinded_elements.iter(), blinded_elements.iter(),
&prepared_evaluation_elements, &prepared_evaluation_elements,
@@ -476,3 +542,56 @@ where
} }
Ok(()) Ok(())
} }
// Tests input, sksm -> output
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result = server.evaluate(&parameters.input[i])?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = VoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result = server.evaluate(&parameters.input[i])?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result =
server.evaluate(&parameters.input[i], Some(&parameters.info))?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
+110 -40
View File
@@ -1,9 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates. // Copyright (c) Meta Platforms, Inc. and affiliates.
// //
// This source code is licensed under both the MIT license found in the // This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache // LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory // License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. // of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Contains the main VOPRF API //! Contains the main VOPRF API
@@ -19,8 +20,9 @@ use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use crate::common::{ use crate::common::{
derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2, verify_proof, derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2,
BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, server_evaluate_hash_input, verify_proof, BlindedElement, EvaluationElement, Mode,
PreparedEvaluationElement, Proof, STR_FINALIZE,
}; };
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar}; use crate::serialization::serde::{Element, Scalar};
@@ -38,7 +40,7 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct VoprfClient<CS: CipherSuite> pub struct VoprfClient<CS: CipherSuite>
where where
@@ -58,7 +60,7 @@ where
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
derive(serde::Deserialize, serde::Serialize), derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "") serde(bound = "")
)] )]
pub struct VoprfServer<CS: CipherSuite> pub struct VoprfServer<CS: CipherSuite>
where where
@@ -253,13 +255,13 @@ where
/// Computes the second step for the multiplicative blinding version of /// Computes the second step for the multiplicative blinding version of
/// DH-OPRF. This message is sent from the server (who holds the OPRF key) /// DH-OPRF. This message is sent from the server (who holds the OPRF key)
/// to the client. /// to the client.
pub fn evaluate<R: RngCore + CryptoRng>( pub fn blind_evaluate<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
blinded_element: &BlindedElement<CS>, blinded_element: &BlindedElement<CS>,
) -> VoprfServerEvaluateResult<CS> { ) -> VoprfServerEvaluateResult<CS> {
let mut prepared_evaluation_elements = let mut prepared_evaluation_elements =
self.batch_evaluate_prepare(iter::once(blinded_element)); self.batch_blind_evaluate_prepare(iter::once(blinded_element));
let prepared_evaluation_element = [prepared_evaluation_elements.next().unwrap()]; let prepared_evaluation_element = [prepared_evaluation_elements.next().unwrap()];
// This can't fail because we know the size of the inputs. // This can't fail because we know the size of the inputs.
@@ -267,7 +269,7 @@ where
mut messages, mut messages,
proof, proof,
} = self } = self
.batch_evaluate_finish( .batch_blind_evaluate_finish(
rng, rng,
iter::once(blinded_element), iter::once(blinded_element),
&prepared_evaluation_element, &prepared_evaluation_element,
@@ -286,7 +288,7 @@ where
/// [`Error::Batch`] if the number of `blinded_elements` and /// [`Error::Batch`] if the number of `blinded_elements` and
/// `evaluation_elements` don't match or is longer then [`u16::MAX`] /// `evaluation_elements` don't match or is longer then [`u16::MAX`]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn batch_evaluate<'a, R: RngCore + CryptoRng, I>( pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, I>(
&self, &self,
rng: &mut R, rng: &mut R,
blinded_elements: &'a I, blinded_elements: &'a I,
@@ -297,10 +299,10 @@ where
<&'a I as IntoIterator>::IntoIter: ExactSizeIterator, <&'a I as IntoIterator>::IntoIter: ExactSizeIterator,
{ {
let prepared_evaluation_elements = self let prepared_evaluation_elements = self
.batch_evaluate_prepare(blinded_elements.into_iter()) .batch_blind_evaluate_prepare(blinded_elements.into_iter())
.collect(); .collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } = self let VoprfServerBatchEvaluateFinishResult { messages, proof } = self
.batch_evaluate_finish::<_, _, Vec<_>>( .batch_blind_evaluate_finish::<_, _, Vec<_>>(
rng, rng,
blinded_elements.into_iter(), blinded_elements.into_iter(),
&prepared_evaluation_elements, &prepared_evaluation_elements,
@@ -310,11 +312,11 @@ where
Ok(VoprfServerBatchEvaluateResult { messages, proof }) Ok(VoprfServerBatchEvaluateResult { messages, proof })
} }
/// Alternative version of `batch_evaluate` without /// Alternative version of `batch_blind_evaluate` without memory allocation.
/// memory allocation. Returned [`PreparedEvaluationElement`] have to be /// Returned [`PreparedEvaluationElement`] have to be
/// [`collect`](Iterator::collect)ed and passed into /// [`collect`](Iterator::collect)ed and passed into
/// [`batch_evaluate_finish`](Self::batch_evaluate_finish). /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish).
pub fn batch_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>( pub fn batch_blind_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>(
&self, &self,
blinded_elements: I, blinded_elements: I,
) -> VoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> ) -> VoprfServerBatchEvaluatePreparedEvaluationElements<CS, I>
@@ -328,13 +330,13 @@ where
}) })
} }
/// See [`batch_evaluate_prepare`](Self::batch_evaluate_prepare) for more /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare)
/// details. /// for more details.
/// ///
/// # Errors /// # Errors
/// [`Error::Batch`] if the number of `blinded_elements` and /// [`Error::Batch`] if the number of `blinded_elements` and
/// `evaluation_elements` don't match or is longer then [`u16::MAX`] /// `evaluation_elements` don't match or is longer then [`u16::MAX`]
pub fn batch_evaluate_finish< pub fn batch_blind_evaluate_finish<
'a, 'a,
'b, 'b,
R: RngCore + CryptoRng, R: RngCore + CryptoRng,
@@ -371,6 +373,22 @@ where
Ok(VoprfServerBatchEvaluateFinishResult { messages, proof }) Ok(VoprfServerBatchEvaluateFinishResult { messages, proof })
} }
/// Computes the output of the POPRF on the server side
///
/// # Errors
/// [`Error::Input`] if the `input` is longer then [`u16::MAX`].
pub fn evaluate(&self, input: &[u8]) -> Result<Output<<CS as CipherSuite>::Hash>> {
let input_element = hash_to_group::<CS>(input, Mode::Voprf)?;
if CS::Group::is_identity_elem(input_element).into() {
return Err(Error::Input);
};
let evaluated_element = input_element * &self.sk;
let issued_element = CS::Group::serialize_elem(evaluated_element);
server_evaluate_hash_input::<CS>(input, None, issued_element)
}
/// Retrieves the server's public key /// Retrieves the server's public key
pub fn get_public_key(&self) -> <CS::Group as Group>::Elem { pub fn get_public_key(&self) -> <CS::Group as Group>::Elem {
self.pk self.pk
@@ -431,7 +449,7 @@ where
} }
/// Concrete type of [`EvaluationElement`]s returned by /// Concrete type of [`EvaluationElement`]s returned by
/// [`VoprfServer::batch_evaluate_prepare`]. /// [`VoprfServer::batch_blind_evaluate_prepare`].
pub type VoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map< pub type VoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
Zip<I, Repeat<<<CS as CipherSuite>::Group as Group>::Scalar>>, Zip<I, Repeat<<<CS as CipherSuite>::Group as Group>::Scalar>>,
fn( fn(
@@ -519,7 +537,7 @@ where
)?; )?;
Ok(blinds Ok(blinds
.zip(messages.into_iter()) .zip(messages)
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
} }
@@ -570,13 +588,12 @@ mod tests {
use ::alloc::vec; use ::alloc::vec;
use ::alloc::vec::Vec; use ::alloc::vec::Vec;
use generic_array::sequence::Concat;
use generic_array::typenum::Sum; use generic_array::typenum::Sum;
use generic_array::ArrayLength; use generic_array::ArrayLength;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use super::*; use super::*;
use crate::common::{create_context_string, STR_HASH_TO_GROUP}; use crate::common::{Dst, STR_HASH_TO_GROUP};
use crate::Group; use crate::Group;
fn prf<CS: CipherSuite>( fn prf<CS: CipherSuite>(
@@ -588,8 +605,8 @@ mod tests {
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>, IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{ {
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode)); let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap(); let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
let res = point * &key; let res = point * &key;
@@ -608,7 +625,7 @@ mod tests {
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap(); let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = VoprfServer::<CS>::new(&mut rng).unwrap(); let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.evaluate(&mut rng, &client_blind_result.message); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message);
let client_finalize_result = client_blind_result let client_finalize_result = client_blind_result
.state .state
.finalize( .finalize(
@@ -642,10 +659,10 @@ mod tests {
} }
let server = VoprfServer::<CS>::new(&mut rng).unwrap(); let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let prepared_evaluation_elements: Vec<_> = server let prepared_evaluation_elements: Vec<_> = server
.batch_evaluate_prepare(client_messages.iter()) .batch_blind_evaluate_prepare(client_messages.iter())
.collect(); .collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } = server let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
.batch_evaluate_finish( .batch_blind_evaluate_finish(
&mut rng, &mut rng,
client_messages.iter(), client_messages.iter(),
&prepared_evaluation_elements, &prepared_evaluation_elements,
@@ -690,10 +707,10 @@ mod tests {
} }
let server = VoprfServer::<CS>::new(&mut rng).unwrap(); let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let prepared_evaluation_elements: Vec<_> = server let prepared_evaluation_elements: Vec<_> = server
.batch_evaluate_prepare(client_messages.iter()) .batch_blind_evaluate_prepare(client_messages.iter())
.collect(); .collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } = server let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
.batch_evaluate_finish( .batch_blind_evaluate_finish(
&mut rng, &mut rng,
client_messages.iter(), client_messages.iter(),
&prepared_evaluation_elements, &prepared_evaluation_elements,
@@ -701,10 +718,9 @@ mod tests {
.unwrap(); .unwrap();
let messages: Vec<_> = messages.collect(); let messages: Vec<_> = messages.collect();
let wrong_pk = { let wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP) let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
.concat(create_context_string::<CS>(Mode::Oprf));
// Choose a group element that is unlikely to be the right public key // Choose a group element that is unlikely to be the right public key
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst).unwrap() CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst.as_dst()).unwrap()
}; };
let client_finalize_result = let client_finalize_result =
VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk); VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk);
@@ -720,12 +736,11 @@ mod tests {
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap(); let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = VoprfServer::<CS>::new(&mut rng).unwrap(); let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.evaluate(&mut rng, &client_blind_result.message); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message);
let wrong_pk = { let wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP) let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
.concat(create_context_string::<CS>(Mode::Oprf));
// Choose a group element that is unlikely to be the right public key // Choose a group element that is unlikely to be the right public key
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst).unwrap() CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst.as_dst()).unwrap()
}; };
let client_finalize_result = client_blind_result.state.finalize( let client_finalize_result = client_blind_result.state.finalize(
input, input,
@@ -736,6 +751,39 @@ mod tests {
assert!(client_finalize_result.is_err()); assert!(client_finalize_result.is_err());
} }
fn verifiable_server_evaluate<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message);
let client_finalize = client_blind_result
.state
.finalize(
input,
&server_result.message,
&server_result.proof,
server.get_public_key(),
)
.unwrap();
// We expect the outputs from client and server to be equal given an identical
// input
let server_evaluate = server.evaluate(input).unwrap();
assert_eq!(client_finalize, server_evaluate);
// We expect the outputs from client and server to be different given different
// inputs
let wrong_input = b"wrong input";
let server_evaluate = server.evaluate(wrong_input).unwrap();
assert!(client_finalize != server_evaluate);
}
fn zeroize_voprf_client<CS: CipherSuite>() fn zeroize_voprf_client<CS: CipherSuite>()
where where
<CS::Hash as OutputSizeUser>::OutputSize: <CS::Hash as OutputSizeUser>::OutputSize:
@@ -769,7 +817,7 @@ mod tests {
let mut rng = OsRng; let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap(); let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = VoprfServer::<CS>::new(&mut rng).unwrap(); let server = VoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.evaluate(&mut rng, &client_blind_result.message); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message);
let mut state = server; let mut state = server;
unsafe { ptr::drop_in_place(&mut state) }; unsafe { ptr::drop_in_place(&mut state) };
@@ -787,6 +835,8 @@ mod tests {
#[test] #[test]
fn test_functionality() -> Result<()> { fn test_functionality() -> Result<()> {
use p256::NistP256; use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")] #[cfg(feature = "ristretto255")]
{ {
@@ -796,6 +846,7 @@ mod tests {
verifiable_batch_retrieval::<Ristretto255>(); verifiable_batch_retrieval::<Ristretto255>();
verifiable_bad_public_key::<Ristretto255>(); verifiable_bad_public_key::<Ristretto255>();
verifiable_batch_bad_public_key::<Ristretto255>(); verifiable_batch_bad_public_key::<Ristretto255>();
verifiable_server_evaluate::<Ristretto255>();
zeroize_voprf_client::<Ristretto255>(); zeroize_voprf_client::<Ristretto255>();
zeroize_voprf_server::<Ristretto255>(); zeroize_voprf_server::<Ristretto255>();
@@ -805,10 +856,29 @@ mod tests {
verifiable_batch_retrieval::<NistP256>(); verifiable_batch_retrieval::<NistP256>();
verifiable_bad_public_key::<NistP256>(); verifiable_bad_public_key::<NistP256>();
verifiable_batch_bad_public_key::<NistP256>(); verifiable_batch_bad_public_key::<NistP256>();
verifiable_server_evaluate::<NistP256>();
zeroize_voprf_client::<NistP256>(); zeroize_voprf_client::<NistP256>();
zeroize_voprf_server::<NistP256>(); zeroize_voprf_server::<NistP256>();
verifiable_retrieval::<NistP384>();
verifiable_batch_retrieval::<NistP384>();
verifiable_bad_public_key::<NistP384>();
verifiable_batch_bad_public_key::<NistP384>();
verifiable_server_evaluate::<NistP384>();
zeroize_voprf_client::<NistP384>();
zeroize_voprf_server::<NistP384>();
verifiable_retrieval::<NistP521>();
verifiable_batch_retrieval::<NistP521>();
verifiable_bad_public_key::<NistP521>();
verifiable_batch_bad_public_key::<NistP521>();
verifiable_server_evaluate::<NistP521>();
zeroize_voprf_client::<NistP521>();
zeroize_voprf_server::<NistP521>();
Ok(()) Ok(())
} }
} }