Compare commits

..
41 Commits
Author SHA1 Message Date
Kevin LewiandGitHub 0a7dc184ca Publishing v0.6.0-pre.1 (#156) 2026-04-06 13:33:53 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f5b7e689e7 Bump actions/cache from 4 to 5 (#148)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [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/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  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>
2026-01-25 14:46:54 -08:00
raphaelrobertandGitHub a22d46fd96 chore: update more dependencies (#145)
* update more dependencies

* cargo fmt

* address review comments

* fix format
2026-01-25 14:37:55 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f23cdfab2d Bump actions/checkout from 4 to 6 (#149)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [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/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  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>
2026-01-23 14:54:37 -08:00
Kevin LewiandGitHub 256ab7bc52 Fixing docs issue (#147) 2025-11-10 01:32:43 -08:00
Kevin LewiandGitHub eb55e9f5b5 Publishing v0.6.0-pre.0 (#146) 2025-11-08 13:46:14 -08:00
raphaelrobertandGitHub e944f9db3b chore: update generic-array to v1 (#143)
* update generic-array to v1

* revert displaydoc removal

* fix cargo fmt
2025-11-04 11:40:16 -08:00
daxpeddaandGitHub f3f4fef0e9 Align Ristretto255::random_scalar() with spec (#142) 2025-05-07 15:40:24 -07:00
daxpeddaandGitHub 23aa7813e7 Enable curve25519-dalek/serde (#141) 2025-04-28 12:48:47 -07:00
daxpeddaandGitHub 0473d9db68 Bump MSRV to v1.83 (#140)
* Fix Clippy warnings for Rust v1.86

* Bump MSRV to v1.83

Signed-off-by: daxpedda <[email protected]>

---------

Signed-off-by: daxpedda <[email protected]>
2025-04-15 13:30:56 -07:00
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
25 changed files with 1648 additions and 1887 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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:
- main
pull_request:
types: [opened, repoened, synchronize]
types: [opened, reopened, synchronize]
jobs:
cargo-audit:
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Cache cargo-audit
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: |
~/.cargo/.crates.toml
@@ -24,7 +24,7 @@ jobs:
run: cargo install cargo-audit
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Run cargo audit
run: cargo audit -D warnings
@@ -35,8 +35,7 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- --features ristretto255-ciphersuite,ristretto255-u64
- --features ristretto255-ciphersuite,ristretto255-u32
- --features ristretto255-ciphersuite
-
frontend_feature:
-
@@ -44,11 +43,11 @@ jobs:
- --features serde
toolchain:
- stable
- 1.57.0
- 1.85.0
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
@@ -75,6 +74,12 @@ jobs:
command: test
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:
name: Build with no-std on ${{ matrix.target }}
runs-on: ubuntu-latest
@@ -88,15 +93,14 @@ jobs:
- thumbv6m-none-eabi
backend_feature:
-
- --features ristretto255-ciphersuite,ristretto255-u64
- --features ristretto255-ciphersuite,ristretto255-u32
- --features ristretto255-ciphersuite
frontend_feature:
-
- --features danger
- --features serde
steps:
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: actions/checkout@v6
- uses: hecrj/setup-rust-action@v2
- run: rustup target add ${{ matrix.target }}
- 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
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
@@ -120,7 +124,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets -- -D warnings
args: --all-features --all-targets -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
@@ -136,7 +140,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
@@ -157,7 +161,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Cache
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: |
~/.cargo/.crates.toml
@@ -166,10 +170,10 @@ jobs:
key: taplo
- name: Install Taplo
run: cargo install taplo-cli
run: cargo install taplo-cli --locked
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Run Taplo
run: taplo fmt --check
+2 -1
View File
@@ -10,9 +10,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
rust: [stable]
steps:
- uses: hecrj/setup-rust-action@v1
- uses: hecrj/setup-rust-action@v2
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
+41
View File
@@ -1,5 +1,46 @@
# Changelog
## 0.6.0-pre.1 (April 6, 2026)
* MSRV bumped to 1.85
* Updated rand_core dependency to 0.9
* Updated rand dependency to 0.9
* Updated subtle dependency to 2.6
* Fixed docs issue
## 0.6.0-pre.0 (November 8, 2025)
* MSRV bumped to 1.83
* Updated Ristretto255 random scalar generation
* Updated generic-array to v1
## 0.5.0 (March 6, 2024)
* 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
+2 -1
View File
@@ -27,4 +27,5 @@ outlined on that page and do not file a public issue.
## License
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.
+30 -23
View File
@@ -7,56 +7,63 @@ keywords = ["oprf"]
license = "MIT"
name = "voprf"
readme = "README.md"
repository = "https://github.com/novifinancial/voprf/"
rust-version = "1.57"
version = "0.4.0"
repository = "https://github.com/facebook/voprf/"
rust-version = "1.85"
version = "0.6.0-pre.1"
[features]
alloc = []
danger = []
default = ["ristretto255-ciphersuite", "ristretto255-u64", "serde"]
ristretto255 = ["curve25519-dalek", "generic-array/more_lengths"]
ristretto255-ciphersuite = ["ristretto255", "sha2"]
ristretto255-fiat-u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"]
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_"]
default = ["ristretto255-ciphersuite", "dep:serde"]
ristretto255 = ["dep:curve25519-dalek"]
ristretto255-ciphersuite = ["ristretto255", "dep:sha2"]
serde = ["curve25519-dalek?/serde", "generic-array/serde", "dep:serde"]
std = ["alloc"]
[dependencies]
curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true }
curve25519-dalek = { version = "4", default-features = false, features = [
"rand_core",
"zeroize",
], optional = true }
derive-where = { version = "1", features = ["zeroize-on-drop"] }
digest = "0.10"
displaydoc = { version = "0.2", default-features = false }
elliptic-curve = { version = "0.12", features = [
elliptic-curve = { version = "0.13", features = [
"hash2curve",
"sec1",
"voprf",
] }
generic-array = "0.14"
rand_core = { version = "0.6", default-features = false }
serde_ = { version = "1", package = "serde", default-features = false, features = [
generic-array = "1"
rand_core = { version = "0.9", default-features = false }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
subtle = { version = "2.6", default-features = false }
zeroize = { version = "1.5", default-features = false }
[dev-dependencies]
generic-array = { version = "0.14", features = ["more_lengths"] }
generic-array = { version = "1" }
hex = "0.4"
json = "0.12"
p256 = { version = "0.11", default-features = false, features = [
p256 = { version = "0.13", 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",
"voprf",
] }
proptest = "1"
rand = "0.8"
rand = "0.9"
regex = "1"
serde_json = "1"
sha2 = "0.10"
[package.metadata.docs.rs]
features = ["danger", "std"]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
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.
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
-------------
@@ -16,12 +16,12 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.4"
voprf = "0.6.0-pre.1"
```
### Minimum Supported Rust Version
Rust **1.57** or higher.
Rust **1.85** or higher.
Contributors
------------
@@ -32,4 +32,6 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
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.
+14 -12
View File
@@ -1,16 +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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
use digest::core_api::BlockSizeUser;
use digest::{Digest, OutputSizeUser};
use digest::{FixedOutput, HashMarker, OutputSizeUser};
use elliptic_curve::VoprfParameters;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::ArrayLength;
use crate::Group;
@@ -18,11 +20,11 @@ use crate::Group;
pub trait CipherSuite
where
<Self::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>,
ArrayLength + IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>,
{
/// The ciphersuite identifier as dictated by
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/>
const ID: u16;
/// <https://www.rfc-editor.org/rfc/rfc9497>
const ID: &'static str;
/// 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`].
@@ -30,17 +32,17 @@ where
/// The main hash function to use (for HKDF computations and hashing
/// transcripts).
type Hash: BlockSizeUser + Digest;
type Hash: BlockSizeUser + Default + FixedOutput + HashMarker;
}
impl<T: VoprfParameters> CipherSuite for T
where
T: Group,
T::Hash: BlockSizeUser + Digest,
T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker,
<T::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
ArrayLength + IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
{
const ID: u16 = T::ID;
const ID: &'static str = T::ID;
type Group = T;
+112 -111
View File
@@ -1,21 +1,22 @@
// 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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.
use core::convert::TryFrom;
use core::ops::Add;
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U11, U2, U256};
use generic_array::typenum::{IsLess, Unsigned, U2, U256, U9};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use rand_core::{TryCryptoRng, TryRngCore};
use subtle::ConstantTimeEq;
#[cfg(feature = "serde")]
@@ -33,7 +34,7 @@ pub(crate) const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair";
pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite";
pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge";
pub(crate) const STR_INFO: [u8; 4] = *b"Info";
pub(crate) const STR_VOPRF: [u8; 8] = *b"VOPRF10-";
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_GROUP: [u8; 12] = *b"HashToGroup-";
@@ -72,15 +73,12 @@ impl Mode {
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct BlindedElement<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
pub(crate) <CS::Group as Group>::Elem,
)
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
);
/// The server's response to the [BlindedElement] message from a client (either
/// verifiable or not) to a server (either verifiable or not).
@@ -89,15 +87,12 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct EvaluationElement<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
pub(crate) <CS::Group as Group>::Elem,
)
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
);
/// Contains prepared [`EvaluationElement`]s by a server batch evaluate
/// preparation.
@@ -106,12 +101,9 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>)
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>);
/// A proof produced by a server that the OPRF output matches against a server
/// public key.
@@ -120,13 +112,9 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct Proof<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct Proof<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) c_scalar: <CS::Group as Group>::Scalar,
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
@@ -140,24 +128,20 @@ where
/// Can only fail with [`Error::Batch`].
#[allow(clippy::many_single_char_names)]
pub(crate) fn generate_proof<CS: CipherSuite, R: RngCore + CryptoRng>(
pub(crate) fn generate_proof<CS: CipherSuite, R: TryRngCore + TryCryptoRng>(
rng: &mut R,
k: <CS::Group as Group>::Scalar,
a: <CS::Group as Group>::Elem,
b: <CS::Group as Group>::Elem,
cs: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
ds: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
mode: Mode,
) -> Result<Proof<CS>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html#section-2.2.1
) -> Result<Proof<CS>> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
let r = CS::Group::random_scalar(rng);
let r = CS::Group::random_scalar(rng)?;
let t2 = a * &r;
let t3 = m * &r;
@@ -194,9 +178,9 @@ where
&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.
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);
Ok(Proof { c_scalar, s_scalar })
@@ -207,16 +191,12 @@ where
pub(crate) fn verify_proof<CS: CipherSuite>(
a: <CS::Group as Group>::Elem,
b: <CS::Group as Group>::Elem,
cs: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
ds: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
proof: &Proof<CS>,
mode: Mode,
) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html#section-2.2.2
) -> Result<()> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.2
let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?;
let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar);
let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar);
@@ -254,9 +234,9 @@ where
&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.
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() {
true => Ok(()),
@@ -280,12 +260,8 @@ fn compute_composites<
c_slice: IC,
d_slice: ID,
mode: Mode,
) -> Result<ComputeCompositesResult<CS>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html#section-2.2.1
) -> Result<ComputeCompositesResult<CS>> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
@@ -296,18 +272,18 @@ where
let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?;
// 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 ||
// I2OSP(len(seedDST), 2) || seedDST
// seed = Hash(h1Input)
let seed = CS::Hash::new()
.chain_update(&elem_len)
.chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(b))
.chain_update(i2osp_2_array(&seed_dst))
.chain_update(seed_dst)
.chain_update(seed_dst.i2osp_2())
.chain_update_multi(&seed_dst.as_dst())
.finalize();
let seed_len = i2osp_2_array(&seed);
let seed_len = i2osp_2_array::<<CS::Hash as OutputSizeUser>::OutputSize>();
let mut m = CS::Group::identity_elem();
let mut z = CS::Group::identity_elem();
@@ -332,9 +308,9 @@ where
&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.
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;
z = match k_option {
Some(_) => z,
@@ -360,13 +336,8 @@ pub(crate) fn derive_key_internal<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>,
{
let context_string = create_context_string::<CS>(mode);
let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string);
) -> Result<<CS::Group as Group>::Scalar, Error> {
let dst = Dst::new::<CS, _, _>(STR_DERIVE_KEYPAIR, mode);
let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;
@@ -376,7 +347,7 @@ where
// || contextString)
let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
&[seed, &info_len, info, &counter.to_be_bytes()],
&dst,
&dst.as_dst(),
)
.map_err(|_| Error::DeriveKeyPair)?;
@@ -388,17 +359,18 @@ where
Err(Error::Protocol)
}
/// Can only fail with [`Error::DeriveKeyPair`] and [`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>,
{
) -> Result<<CS::Group as Group>::Scalar, Error> {
derive_key_internal::<CS>(seed, info, mode)
}
@@ -412,11 +384,7 @@ pub(crate) fn derive_keypair<CS: CipherSuite>(
seed: &[u8],
info: &[u8],
mode: Mode,
) -> Result<DeriveKeypairResult<CS>, Error>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> Result<DeriveKeypairResult<CS>, Error> {
let sk_s = derive_key_internal::<CS>(seed, info, mode)?;
let pk_s = CS::Group::base_elem() * &sk_s;
@@ -432,11 +400,7 @@ pub(crate) fn deterministic_blind_unchecked<CS: CipherSuite>(
input: &[u8],
blind: &<CS::Group as Group>::Scalar,
mode: Mode,
) -> Result<<CS::Group as Group>::Elem>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> Result<<CS::Group as Group>::Elem> {
let hashed_point = hash_to_group::<CS>(input, mode)?;
Ok(hashed_point * blind)
}
@@ -445,13 +409,9 @@ where
pub(crate) fn hash_to_group<CS: CipherSuite>(
input: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Elem>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).map_err(|_| Error::Input)
) -> Result<<CS::Group as Group>::Elem> {
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
}
/// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF.
@@ -460,11 +420,7 @@ 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>,
{
) -> Result<Output<CS::Hash>> {
// OPRF & VOPRF
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(issuedElement), 2) || issuedElement ||
@@ -486,22 +442,69 @@ where
.chain_update(info.as_ref());
}
Ok(hash
.chain_update(i2osp_2(issued_element.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(i2osp_2(issued_element.as_slice().len()).map_err(|_| Error::Input)?)
.chain_update(issued_element)
.chain_update(STR_FINALIZE)
.finalize())
}
/// Generates the contextString parameter as defined in
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/>
pub(crate) fn create_context_string<CS: CipherSuite>(mode: Mode) -> GenericArray<u8, U11>
pub(crate) struct Dst<L: ArrayLength> {
dst_1: GenericArray<u8, L>,
dst_2: &'static str,
}
impl<L: ArrayLength> Dst<L> {
pub(crate) fn new<CS, T, TL>(par_1: T, mode: Mode) -> Self
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
CS: CipherSuite,
T: Into<GenericArray<u8, TL>>,
TL: ArrayLength + Add<U9, Output = L>,
{
GenericArray::from(STR_VOPRF)
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(CS::ID.to_be_bytes().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
}
}
///////////////////////
@@ -515,8 +518,6 @@ pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
.map_err(|_| InternalError::I2osp)
}
pub(crate) fn i2osp_2_array<L: ArrayLength<u8> + IsLess<U256>>(
_: &GenericArray<u8, L>,
) -> GenericArray<u8, U2> {
pub(crate) fn i2osp_2_array<L: ArrayLength + IsLess<U256>>() -> GenericArray<u8, U2> {
L::U16.to_be_bytes().into()
}
+9 -9
View File
@@ -1,19 +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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
use displaydoc::Display;
/// [`Result`](core::result::Result) shorthand that uses [`Error`].
pub type Result<T, E = Error> = core::result::Result<T, E>;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Clone, Copy, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, displaydoc::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Error {
/// Size of info is longer then [`u16::MAX`].
Info,
@@ -29,6 +28,8 @@ pub enum Error {
ProofVerification,
/// The protocol has failed and can't be completed.
Protocol,
/// Random number generator failure.
Rng,
}
/// Only used to implement [`Group`](crate::Group).
@@ -40,5 +41,4 @@ pub enum InternalError {
I2osp,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::error::Error for Error {}
+80 -22
View File
@@ -1,55 +1,71 @@
// 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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 core::num::NonZeroU32;
use core::ops::Add;
use digest::core_api::BlockSizeUser;
use digest::Digest;
use digest::{FixedOutput, HashMarker};
use elliptic_curve::group::cofactor::CofactorGroup;
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{
AffinePoint, Field, FieldSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey,
AffinePoint, Field, FieldBytes, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar,
SecretKey,
};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{TryCryptoRng, TryRngCore};
use super::Group;
use crate::{Error, InternalError, Result};
type ElemLen<C> = <ScalarLen<C> as ModulusSize>::CompressedPointSize;
type ScalarLen<C> = FieldBytesSize<C>;
impl<C> Group for C
where
C: GroupDigest,
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
FieldSize<Self>: ModulusSize,
ScalarLen<Self>: ModulusSize,
ScalarLen<Self>: ArrayLength,
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
Scalar<Self>: FromOkm,
// `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen`
ScalarLen<Self>: Add<ElemLen<Self>>,
Sum<ScalarLen<Self>, ElemLen<Self>>: ArrayLength,
// `ProofLen`
ScalarLen<Self>: Add<ScalarLen<Self>>,
Sum<ScalarLen<Self>, ScalarLen<Self>>: ArrayLength,
ElemLen<Self>: ArrayLength,
{
type Elem = ProjectivePoint<Self>;
type ElemLen = <FieldSize<Self> as ModulusSize>::CompressedPointSize;
type ElemLen = ElemLen<Self>;
type Scalar = Scalar<Self>;
type ScalarLen = FieldSize<Self>;
type ScalarLen = ScalarLen<Self>;
// Implements the `hash_to_curve()` function from
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
// https://www.rfc-editor.org/rfc/rfc9380.html#section-3
fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
Self::hash_from_bytes::<ExpandMsgXmd<H>>(input, dst).map_err(|_| InternalError::Input)
}
// 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
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
<Self as GroupDigest>::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
@@ -78,8 +94,8 @@ where
.map_err(|_| Error::Deserialization)
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
*SecretKey::<Self>::random(rng).to_nonzero_scalar()
fn random_scalar<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self::Scalar> {
Ok(*SecretKey::<Self>::random(&mut CompatRng(rng)).to_nonzero_scalar())
}
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
@@ -92,16 +108,58 @@ where
#[cfg(test)]
fn zero_scalar() -> Self::Scalar {
Scalar::<Self>::zero()
Scalar::<Self>::ZERO
}
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.into()
let bytes: FieldBytes<Self> = scalar.into();
let mut result = GenericArray::<u8, Self::ScalarLen>::default();
result.as_mut_slice().copy_from_slice(bytes.as_ref());
result
}
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_err(|_| Error::Deserialization)
}
}
/// Adapter allowing `rand_core 0.9` RNGs to satisfy the `elliptic_curve` 0.13
/// requirement for `rand_core 0.6` traits.
///
/// TODO #150: Remove this adapter when `elliptic_curve` migrates to `rand_core
/// 0.9`.
struct CompatRng<'a, R>(&'a mut R);
impl<'a, R> elliptic_curve::rand_core::RngCore for CompatRng<'a, R>
where
R: TryRngCore,
{
fn next_u32(&mut self) -> u32 {
self.0.try_next_u32().expect("RNG failure")
}
fn next_u64(&mut self) -> u64 {
self.0.try_next_u64().expect("RNG failure")
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0
.try_fill_bytes(dest)
.expect("RNG failure while filling bytes");
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), elliptic_curve::rand_core::Error> {
self.0.try_fill_bytes(dest).map_err(|_| compat_error())?;
Ok(())
}
}
impl<'a, R> elliptic_curve::rand_core::CryptoRng for CompatRng<'a, R> where R: TryCryptoRng {}
fn compat_error() -> elliptic_curve::rand_core::Error {
let code = NonZeroU32::new(elliptic_curve::rand_core::Error::CUSTOM_START)
.expect("CUSTOM_START must be non-zero");
elliptic_curve::rand_core::Error::from(code)
}
+29 -17
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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
@@ -14,10 +15,10 @@ mod ristretto;
use core::ops::{Add, Mul, Sub};
use digest::core_api::BlockSizeUser;
use digest::Digest;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use digest::{FixedOutput, HashMarker};
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use rand_core::{TryCryptoRng, TryRngCore};
#[cfg(feature = "ristretto255")]
pub use ristretto::Ristretto255;
use subtle::{Choice, ConstantTimeEq};
@@ -26,8 +27,16 @@ use zeroize::Zeroize;
use crate::{InternalError, Result};
/// 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.
pub trait Group {
/// subgroup is noted additively — as in the RFC — in this trait.
pub trait Group
where
// `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen`
Self::ScalarLen: Add<Self::ElemLen>,
Sum<Self::ScalarLen, Self::ElemLen>: ArrayLength,
// `ProofLen`
Self::ScalarLen: Add<Self::ScalarLen>,
Sum<Self::ScalarLen, Self::ScalarLen>: ArrayLength,
{
/// The type of group elements
type Elem: ConstantTimeEq
+ Copy
@@ -36,7 +45,7 @@ pub trait Group {
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8> + 'static;
type ElemLen: ArrayLength + 'static;
/// The type of base field scalars
type Scalar: ConstantTimeEq
@@ -47,16 +56,16 @@ pub trait Group {
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;
type ScalarLen: ArrayLength + 'static;
/// Transforms a password and domain separation tag (DST) into a curve point
///
/// # Errors
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
/// 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
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
/// Hashes a slice of pseudo-random bytes to a scalar
@@ -64,9 +73,9 @@ pub trait Group {
/// # Errors
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
/// 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
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
/// Get the base point for the group
@@ -91,8 +100,11 @@ pub trait Group {
/// is not a valid point on the group or the identity element.
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem>;
/// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Picks a scalar at random.
///
/// # Errors
/// [`Error::Rng`](crate::Error::Rng) if the random number generator fails.
fn random_scalar<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self::Scalar>;
/// The multiplicative inverse of this scalar
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar;
+25 -29
View File
@@ -1,20 +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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::Identity;
use digest::core_api::BlockSizeUser;
use digest::Digest;
use digest::{FixedOutput, HashMarker};
use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use rand_core::{TryCryptoRng, TryRngCore};
use subtle::ConstantTimeEq;
use super::Group;
@@ -22,21 +23,17 @@ use crate::{Error, InternalError, Result};
/// [`Group`] implementation for Ristretto255.
#[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;
#[cfg(feature = "ristretto255-ciphersuite")]
impl crate::CipherSuite for Ristretto255 {
const ID: u16 = 0x0001;
const ID: &'static str = "ristretto255-SHA512";
type Group = Ristretto255;
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 {
type Elem = RistrettoPoint;
@@ -47,10 +44,10 @@ impl Group for Ristretto255 {
type ScalarLen = U32;
// Implements the `hash_to_ristretto255()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
// https://www.rfc-editor.org/rfc/rfc9380.html#appendix-B
fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let mut uniform_bytes = GenericArray::<_, U64>::default();
@@ -62,10 +59,10 @@ impl Group for Ristretto255 {
}
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError>
// https://www.rfc-editor.org/rfc/rfc9497#section-4.1
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where
H: Digest + BlockSizeUser,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let mut uniform_bytes = GenericArray::<_, U64>::default();
@@ -90,22 +87,21 @@ impl Group for Ristretto255 {
}
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
if element_bits.len() != 32 {
return Err(Error::Deserialization);
}
CompressedRistretto::from_slice(element_bits)
.map_err(|_| Error::Deserialization)?
.decompress()
.filter(|point| point != &RistrettoPoint::identity())
.ok_or(Error::Deserialization)
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
fn random_scalar<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self::Scalar> {
loop {
let scalar = Scalar::random(rng);
let mut scalar_bytes = [0u8; 32];
rng.try_fill_bytes(&mut scalar_bytes)
.map_err(|_| Error::Rng)?;
if scalar != Scalar::zero() {
break scalar;
if let Ok(scalar) = Self::deserialize_scalar(&scalar_bytes) {
break Ok(scalar);
}
}
}
@@ -115,12 +111,12 @@ impl Group for Ristretto255 {
}
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
scalar.ct_eq(&Scalar::zero())
scalar.ct_eq(&Scalar::ZERO)
}
#[cfg(test)]
fn zero_scalar() -> Self::Scalar {
Scalar::zero()
Scalar::ZERO
}
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
@@ -131,8 +127,8 @@ impl Group for Ristretto255 {
scalar_bits
.try_into()
.ok()
.and_then(Scalar::from_canonical_bytes)
.filter(|scalar| scalar != &Scalar::zero())
.and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
.filter(|scalar| scalar != &Scalar::ZERO)
.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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
@@ -15,6 +16,8 @@ use crate::{Error, Group, Result};
#[test]
fn test_group_properties() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")]
{
@@ -27,6 +30,12 @@ fn test_group_properties() -> Result<()> {
test_identity_element_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(())
}
+12 -25
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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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)
//!
//! Note: This implementation is in sync with
//! [draft-irtf-cfrg-voprf-11](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html),
//! but this specification is subject to change, until the final version
//! published by the IETF.
//! [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497).
//!
//! # Overview
//!
@@ -511,7 +510,7 @@
//! and [PoprfClient] are used, and that each of the functions accept an
//! additional (and optional) info parameter which represents the public input.
//! See
//! <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html#name-poprf-public-input>
//! <https://www.rfc-editor.org/rfc/rfc9497#name-poprf-public-input>
//! for more detailed information on how this public input should be used.
//!
//! # Features
@@ -532,24 +531,15 @@
//! a [`CipherSuite`].
//!
//! - The `ristretto255` feature enables using [`Ristretto255`] as the
//! underlying group for the [Group] choice. A backend feature, which are
//! re-exported from [curve25519-dalek] and allow for selecting the
//! 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
//! enables parallel formulas, using either AVX2 or AVX512-IFMA. This will
//! automatically enable the `ristretto255-u64` feature and requires Rust
//! nightly.
//! underlying group for the [Group] choice. To select a specific backend see
//! the [curve25519-dalek] documentation.
//!
//! [curve25519-dalek]:
//! (https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features)
//! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends)
#![cfg_attr(not(test), deny(unsafe_code))]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(test), deny(unsafe_code))]
#![warn(
clippy::cargo,
clippy::missing_errors_doc,
@@ -564,9 +554,6 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "serde")]
extern crate serde_ as serde;
mod ciphersuite;
mod common;
mod error;
+51 -84
View File
@@ -1,20 +1,20 @@
// 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
use core::iter::{self, Map};
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser};
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
use digest::{Digest, Output};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use rand_core::{TryCryptoRng, TryRngCore};
use crate::common::{
derive_key_internal, deterministic_blind_unchecked, hash_to_group, i2osp_2,
@@ -41,13 +41,9 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct OprfClient<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct OprfClient<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) blind: <CS::Group as Group>::Scalar,
}
@@ -59,13 +55,9 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct OprfServer<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct OprfServer<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) sk: <CS::Group as Group>::Scalar,
}
@@ -75,21 +67,17 @@ where
// =================== //
/////////////////////////
impl<CS: CipherSuite> OprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> OprfClient<CS> {
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF.
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
pub fn blind<R: RngCore + CryptoRng>(
pub fn blind<R: TryRngCore + TryCryptoRng>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<OprfClientBlindResult<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)
}
@@ -153,18 +141,14 @@ where
}
}
impl<CS: CipherSuite> OprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> OprfServer<CS> {
/// Produces a new instance of a [OprfServer] using a supplied RNG
///
/// # Errors
/// [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self> {
pub fn new<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self> {
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
rng.fill_bytes(&mut seed);
rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
Self::new_from_seed(&seed, &[])
}
@@ -193,7 +177,7 @@ where
Ok(Self { sk })
}
// Only used for tests
/// Only used for tests
#[cfg(test)]
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
self.sk
@@ -230,11 +214,7 @@ where
/// Contains the fields that are returned by a non-verifiable client blind
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
pub struct OprfClientBlindResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct OprfClientBlindResult<CS: CipherSuite> {
/// The state to be persisted on the client
pub state: OprfClient<CS>,
/// The message to send to the server
@@ -260,11 +240,7 @@ fn finalize_after_unblind<
>(
inputs_and_unblinded_elements: IE,
_unused: &'a [u8],
) -> FinalizeAfterUnblindResult<CS, I, IE>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> FinalizeAfterUnblindResult<'a, CS, I, IE> {
inputs_and_unblinded_elements.map(|(input, unblinded_element)| {
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
@@ -277,7 +253,7 @@ where
.chain_update(input.as_ref())
.chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(unblinded_element))
.chain_update(&STR_FINALIZE)
.chain_update(STR_FINALIZE)
.finalize())
})
}
@@ -291,11 +267,11 @@ where
mod tests {
use core::ptr;
use generic_array::sequence::Concat;
use rand::rngs::OsRng;
use rand::TryRngCore;
use super::*;
use crate::common::{create_context_string, STR_HASH_TO_GROUP};
use crate::common::{Dst, STR_HASH_TO_GROUP};
use crate::Group;
fn prf<CS: CipherSuite>(
@@ -303,13 +279,9 @@ mod tests {
key: <CS::Group as Group>::Scalar,
info: &[u8],
mode: Mode,
) -> Output<CS::Hash>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap();
) -> Output<CS::Hash> {
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
let res = point * &key;
@@ -319,11 +291,7 @@ mod tests {
.unwrap()
}
fn base_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn base_retrieval<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -334,23 +302,18 @@ mod tests {
assert_eq!(client_finalize_result, res2);
}
fn base_inversion_unsalted<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn base_inversion_unsalted<CS: CipherSuite>() {
let mut rng = OsRng;
let mut input = [0u8; 64];
rng.fill_bytes(&mut input);
rng.try_fill_bytes(&mut input).unwrap();
let client_blind_result = OprfClient::<CS>::blind(&input, &mut rng).unwrap();
let client_finalize_result = client_blind_result
.state
.finalize(&input, &EvaluationElement(client_blind_result.message.0))
.unwrap();
let dst =
GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(Mode::Oprf));
let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst).unwrap();
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst.as_dst()).unwrap();
let res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[])
.next()
.unwrap()
@@ -359,11 +322,7 @@ mod tests {
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>,
{
fn server_evaluate<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -387,11 +346,7 @@ mod tests {
assert!(client_finalize != server_evaluate);
}
fn zeroize_oprf_client<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn zeroize_oprf_client<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -405,11 +360,7 @@ mod tests {
assert!(message.serialize().iter().all(|&x| x == 0));
}
fn zeroize_oprf_server<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn zeroize_oprf_server<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -427,6 +378,8 @@ mod tests {
#[test]
fn test_functionality() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")]
{
@@ -447,6 +400,20 @@ mod tests {
zeroize_oprf_client::<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(())
}
}
+73 -147
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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
@@ -12,18 +13,15 @@ use alloc::vec::Vec;
use core::iter::{self, Map, Repeat, Zip};
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use generic_array::typenum::Unsigned;
use generic_array::{ArrayLength, GenericArray};
use rand_core::{TryCryptoRng, TryRngCore};
use crate::common::{
create_context_string, derive_keypair, deterministic_blind_unchecked, generate_proof,
hash_to_group, i2osp_2, server_evaluate_hash_input, verify_proof, BlindedElement,
EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR,
STR_INFO,
derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2,
server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode,
PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO,
};
#[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar};
@@ -41,13 +39,9 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct PoprfClient<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfClient<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) blind: <CS::Group as Group>::Scalar,
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -61,13 +55,9 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct PoprfServer<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfServer<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) sk: <CS::Group as Group>::Scalar,
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -79,21 +69,17 @@ where
// =================== //
/////////////////////////
impl<CS: CipherSuite> PoprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> PoprfClient<CS> {
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF.
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
pub fn blind<R: RngCore + CryptoRng>(
pub fn blind<R: TryRngCore + TryCryptoRng>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> 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)
}
@@ -146,7 +132,10 @@ where
proof: &Proof<CS>,
pk: <CS::Group as Group>::Elem,
info: Option<&[u8]>,
) -> Result<Output<CS::Hash>> {
) -> Result<Output<CS::Hash>>
where
<<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArrayLength,
{
let clients = core::array::from_ref(self);
let messages = core::array::from_ref(evaluation_element);
@@ -181,6 +170,7 @@ where
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
<&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
<<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArrayLength,
{
let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?;
@@ -194,18 +184,14 @@ where
}
}
impl<CS: CipherSuite> PoprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> PoprfServer<CS> {
/// Produces a new instance of a [PoprfServer] using a supplied RNG
///
/// # Errors
/// [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self> {
pub fn new<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self> {
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
rng.fill_bytes(&mut seed);
rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
Self::new_from_seed(&seed, &[])
}
@@ -236,7 +222,7 @@ where
Ok(Self { sk, pk })
}
// Only used for tests
/// Only used for tests
#[cfg(test)]
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
self.sk
@@ -249,7 +235,7 @@ where
/// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn blind_evaluate<R: RngCore + CryptoRng>(
pub fn blind_evaluate<R: TryRngCore + TryCryptoRng>(
&self,
rng: &mut R,
blinded_element: &BlindedElement<CS>,
@@ -287,7 +273,7 @@ where
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
#[cfg(feature = "alloc")]
pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, IE>(
pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, IE>(
&self,
rng: &mut R,
blinded_elements: &'a IE,
@@ -360,7 +346,7 @@ where
pub fn batch_blind_evaluate_finish<
'a,
'b,
R: RngCore + CryptoRng,
R: TryRngCore + TryCryptoRng,
IB: Iterator<Item = &'a BlindedElement<CS>> + ExactSizeIterator,
IE,
>(
@@ -428,11 +414,7 @@ where
}
}
impl<CS: CipherSuite> BlindedElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> BlindedElement<CS> {
/// Creates a [BlindedElement] from a raw group element.
///
/// # Caution
@@ -451,11 +433,7 @@ where
}
}
impl<CS: CipherSuite> EvaluationElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> EvaluationElement<CS> {
/// Creates an [EvaluationElement] from a raw group element.
///
/// # Caution
@@ -481,11 +459,7 @@ where
/// Contains the fields that are returned by a verifiable client blind
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
pub struct PoprfClientBlindResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfClientBlindResult<CS: CipherSuite> {
/// The state to be persisted on the client
pub state: PoprfClient<CS>,
/// The message to send to the server
@@ -498,11 +472,7 @@ pub type PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM> =
/// Contains the fields that are returned by a verifiable server evaluate
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
pub struct PoprfServerEvaluateResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfServerEvaluateResult<CS: CipherSuite> {
/// The message to send to the client
pub message: EvaluationElement<CS>,
/// The proof for the client to verify
@@ -512,11 +482,7 @@ where
/// Contains the fields that are returned by a verifiable server batch evaluate
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
#[cfg(feature = "alloc")]
pub struct PoprfServerBatchEvaluateResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfServerBatchEvaluateResult<CS: CipherSuite> {
/// The messages to send to the client
pub messages: Vec<EvaluationElement<CS>>,
/// The proof for the client to verify
@@ -541,24 +507,17 @@ pub type PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct PoprfPreparedTweak<CS: CipherSuite>(
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
<CS::Group as Group>::Scalar,
)
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
);
/// Contains the fields that are returned by a partially verifiable server batch
/// evaluate prepare
#[derive_where(Debug; I, <CS::Group as Group>::Scalar)]
pub struct PoprfServerBatchEvaluatePrepareResult<CS: CipherSuite, I>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct PoprfServerBatchEvaluatePrepareResult<CS: CipherSuite, I> {
/// Prepared [`EvaluationElement`].
pub prepared_evaluation_elements: PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I>,
/// Prepared tweak.
@@ -577,8 +536,6 @@ pub type PoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map<
#[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, <CS::Group as Group>::Scalar)]
pub struct PoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
&'a I: IntoIterator<Item = &'a PreparedEvaluationElement<CS>>,
{
/// The [`EvaluationElement`]s to send to the client
@@ -599,11 +556,7 @@ where
fn compute_tweaked_key<CS: CipherSuite>(
pk: <CS::Group as Group>::Elem,
info: Option<&[u8]>,
) -> Result<<CS::Group as Group>::Elem>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> Result<<CS::Group as Group>::Elem> {
// None for info is treated the same as empty bytes
let info = info.unwrap_or_default();
@@ -616,10 +569,9 @@ where
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
let framed_info = [STR_INFO.as_slice(), &info_len, info];
let dst =
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
// 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 tweaked_key = t + &pk;
@@ -638,11 +590,7 @@ where
fn compute_tweak<CS: CipherSuite>(
sk: <CS::Group as Group>::Scalar,
info: Option<&[u8]>,
) -> Result<<CS::Group as Group>::Scalar>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> Result<<CS::Group as Group>::Scalar> {
// None for info is treated the same as empty bytes
let info = info.unwrap_or_default();
@@ -654,10 +602,9 @@ where
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
let framed_info = [STR_INFO.as_slice(), &info_len, info];
let dst =
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
// 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;
@@ -694,8 +641,6 @@ fn poprf_unblind<'a, CS: 'a + CipherSuite, IC, IM>(
info: Option<&[u8]>,
) -> Result<PoprfUnblindResult<'a, CS, IC, IM>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
&'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
@@ -723,7 +668,7 @@ where
)?;
Ok(blinds
.zip(messages.into_iter())
.zip(messages)
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
}
@@ -731,7 +676,7 @@ type FinalizeAfterUnblindResult<'a, CS, IE, II> = Map<
Zip<Zip<IE, II>, Repeat<&'a [u8]>>,
fn(
((<<CS as CipherSuite>::Group as Group>::Elem, &[u8]), &[u8]),
) -> Result<GenericArray<u8, <<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize>>,
) -> Result<Output<<CS as CipherSuite>::Hash>>,
>;
/// Can only fail with [`Error::Batch`] and returned values can only fail with
@@ -747,8 +692,7 @@ fn finalize_after_unblind<
info: Option<&'a [u8]>,
) -> Result<FinalizeAfterUnblindResult<'a, CS, IE, II>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArrayLength,
{
if unblinded_elements.len() != inputs.len() {
return Err(Error::Batch);
@@ -787,11 +731,8 @@ where
#[cfg(test)]
mod tests {
use core::ops::Add;
use core::ptr;
use generic_array::typenum::Sum;
use generic_array::ArrayLength;
use rand::rngs::OsRng;
use super::*;
@@ -803,15 +744,11 @@ mod tests {
key: <CS::Group as Group>::Scalar,
info: &[u8],
mode: Mode,
) -> Output<CS::Hash>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> Output<CS::Hash> {
let t = compute_tweak::<CS>(key, Some(info)).unwrap();
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap();
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
// evaluatedElement = G.ScalarInverse(t) * blindedElement
let res = point * &CS::Group::invert_scalar(t);
@@ -823,11 +760,7 @@ mod tests {
.unwrap()
}
fn verifiable_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_retrieval<CS: CipherSuite>() {
let input = b"input";
let info = b"info";
let mut rng = OsRng;
@@ -850,11 +783,7 @@ mod tests {
assert_eq!(client_finalize_result, res2);
}
fn verifiable_bad_public_key<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_bad_public_key<CS: CipherSuite>() {
let input = b"input";
let info = b"info";
let mut rng = OsRng;
@@ -864,10 +793,9 @@ mod tests {
.blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap();
let wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP)
.concat(create_context_string::<CS>(Mode::Oprf));
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
// 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(
input,
@@ -879,11 +807,7 @@ mod tests {
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>,
{
fn verifiable_server_evaluate<CS: CipherSuite>() {
let input = b"input";
let info = Some(b"info".as_slice());
let mut rng = OsRng;
@@ -916,13 +840,7 @@ mod tests {
assert!(client_finalize != server_evaluate);
}
fn zeroize_verifiable_client<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
{
fn zeroize_verifiable_client<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -936,15 +854,7 @@ mod tests {
assert!(message.serialize().iter().all(|&x| x == 0));
}
fn zeroize_verifiable_server<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
fn zeroize_verifiable_server<CS: CipherSuite>() {
let input = b"input";
let info = b"info";
let mut rng = OsRng;
@@ -970,6 +880,8 @@ mod tests {
#[test]
fn test_functionality() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")]
{
@@ -990,6 +902,20 @@ mod tests {
zeroize_verifiable_client::<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(())
}
}
+25 -82
View File
@@ -1,20 +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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
//! protocol
use core::ops::Add;
use digest::core_api::BlockSizeUser;
use digest::OutputSizeUser;
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, Unsigned, U256};
use generic_array::{ArrayLength, GenericArray};
use generic_array::typenum::{Sum, Unsigned};
use generic_array::GenericArray;
use crate::{
BlindedElement, CipherSuite, Error, EvaluationElement, Group, OprfClient, OprfServer,
@@ -29,11 +26,7 @@ use crate::{
/// Length of [`OprfClient`] in bytes for serialization.
pub type OprfClientLen<CS> = <<CS as CipherSuite>::Group as Group>::ScalarLen;
impl<CS: CipherSuite> OprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> OprfClient<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, OprfClientLen<CS>> {
CS::Group::serialize_scalar(self.blind)
@@ -56,17 +49,9 @@ pub type VoprfClientLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ElemLen,
>;
impl<CS: CipherSuite> VoprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> VoprfClient<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, VoprfClientLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
VoprfClientLen<CS>: ArrayLength<u8>,
{
pub fn serialize(&self) -> GenericArray<u8, VoprfClientLen<CS>> {
<CS::Group as Group>::serialize_scalar(self.blind)
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
}
@@ -92,17 +77,9 @@ pub type PoprfClientLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ElemLen,
>;
impl<CS: CipherSuite> PoprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> PoprfClient<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, PoprfClientLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
PoprfClientLen<CS>: ArrayLength<u8>,
{
pub fn serialize(&self) -> GenericArray<u8, PoprfClientLen<CS>> {
<CS::Group as Group>::serialize_scalar(self.blind)
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
}
@@ -125,11 +102,7 @@ where
/// Length of [`OprfServer`] in bytes for serialization.
pub type OprfServerLen<CS> = <<CS as CipherSuite>::Group as Group>::ScalarLen;
impl<CS: CipherSuite> OprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> OprfServer<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, OprfServerLen<CS>> {
CS::Group::serialize_scalar(self.sk)
@@ -152,17 +125,9 @@ pub type VoprfServerLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ElemLen,
>;
impl<CS: CipherSuite> VoprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> VoprfServer<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, VoprfServerLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
VoprfServerLen<CS>: ArrayLength<u8>,
{
pub fn serialize(&self) -> GenericArray<u8, VoprfServerLen<CS>> {
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
}
@@ -184,17 +149,9 @@ pub type PoprfServerLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ElemLen,
>;
impl<CS: CipherSuite> PoprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> PoprfServer<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, PoprfServerLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
PoprfServerLen<CS>: ArrayLength<u8>,
{
pub fn serialize(&self) -> GenericArray<u8, PoprfServerLen<CS>> {
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
}
@@ -216,17 +173,9 @@ pub type ProofLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
>;
impl<CS: CipherSuite> Proof<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> Proof<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, ProofLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
ProofLen<CS>: ArrayLength<u8>,
{
pub fn serialize(&self) -> GenericArray<u8, ProofLen<CS>> {
CS::Group::serialize_scalar(self.c_scalar)
.concat(CS::Group::serialize_scalar(self.s_scalar))
}
@@ -246,11 +195,7 @@ where
/// Length of [`BlindedElement`] in bytes for serialization.
pub type BlindedElementLen<CS> = <<CS as CipherSuite>::Group as Group>::ElemLen;
impl<CS: CipherSuite> BlindedElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> BlindedElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, BlindedElementLen<CS>> {
CS::Group::serialize_elem(self.0)
@@ -270,11 +215,7 @@ where
/// Length of [`EvaluationElement`] in bytes for serialization.
pub type EvaluationElementLen<CS> = <<CS as CipherSuite>::Group as Group>::ElemLen;
impl<CS: CipherSuite> EvaluationElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> EvaluationElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, EvaluationElementLen<CS>> {
CS::Group::serialize_elem(self.0)
@@ -306,11 +247,11 @@ fn deserialize_scalar<G: Group>(input: &mut &[u8]) -> Result<G::Scalar> {
}
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] {
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() {
return None;
}
@@ -389,6 +330,8 @@ mod test {
}
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
let _ = $item::<p384::NistP384>::deserialize(&$bytes[..]);
let _ = $item::<p521::NistP521>::deserialize(&$bytes[..]);
};
}
+642 -754
View File
File diff suppressed because it is too large Load Diff
+7 -12
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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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 core::cmp::min;
use rand_core::{CryptoRng, Error, RngCore};
use rand_core::{CryptoRng, RngCore};
/// A simple implementation of `RngCore` for testing purposes.
///
@@ -50,15 +51,9 @@ impl RngCore for CycleRng {
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
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);
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
}
// This is meant for testing only
+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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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::vec::Vec;
@@ -14,7 +15,7 @@ pub(crate) fn rfc_to_json(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 chunks: Vec<&str> = re.split(input).collect();
@@ -33,7 +34,7 @@ fn parse_ciphersuites(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 chunks: Vec<&str> = re.split(input).collect();
@@ -52,7 +53,7 @@ fn parse_modes(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 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 val = iter.next().unwrap().split_whitespace().next().unwrap();
param = format!(" \"{}\": \"{}", key, val);
param = format!(" \"{key}\": \"{val}");
} else {
let s = line.trim().to_string();
if s.contains('~') || s.contains('#') {
+94 -104
View File
@@ -1,20 +1,16 @@
// 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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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::vec;
use alloc::vec::Vec;
use core::ops::Add;
use digest::core_api::BlockSizeUser;
use digest::OutputSizeUser;
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256};
use generic_array::ArrayLength;
use json::JsonValue;
use serde_json::Value;
use crate::tests::mock_rng::CycleRng;
use crate::tests::parser::*;
@@ -40,7 +36,7 @@ struct VOPRFTestVectorParameters {
output: Vec<Vec<u8>>,
}
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
seed: decode(values, "Seed"),
sksm: decode(values, "skSm"),
@@ -57,18 +53,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]
.as_str()
.and_then(|s| hex::decode(&s).ok())
.and_then(|s| hex::decode(s).ok())
.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 res = match s.contains(',') {
true => Some(s.split(',').map(|x| hex::decode(&x).unwrap()).collect()),
false => Some(vec![hex::decode(&s).unwrap()]),
true => Some(s.split(',').map(|x| hex::decode(x).unwrap()).collect()),
false => Some(vec![hex::decode(s).unwrap()]),
};
res.unwrap()
}
@@ -76,8 +72,10 @@ fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> {
macro_rules! json_to_test_vectors {
( $v:ident, $cs:expr, $mode:expr ) => {
$v[$cs][$mode]
.members()
.map(|x| populate_test_vectors(&x))
.as_array()
.into_iter()
.flatten()
.map(populate_test_vectors)
.collect::<Vec<VOPRFTestVectorParameters>>()
};
}
@@ -85,8 +83,10 @@ macro_rules! json_to_test_vectors {
#[test]
fn test_vectors() -> Result<()> {
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");
#[cfg(feature = "ristretto255")]
@@ -95,7 +95,7 @@ fn test_vectors() -> Result<()> {
let ristretto_oprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("ristretto255-SHA512"),
String::from("OPRF")
);
assert_ne!(ristretto_oprf_tvs.len(), 0);
@@ -107,7 +107,7 @@ fn test_vectors() -> Result<()> {
let ristretto_voprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("ristretto255-SHA512"),
String::from("VOPRF")
);
assert_ne!(ristretto_voprf_tvs.len(), 0);
@@ -119,7 +119,7 @@ fn test_vectors() -> Result<()> {
let ristretto_poprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("ristretto255-SHA512"),
String::from("POPRF")
);
assert_ne!(ristretto_poprf_tvs.len(), 0);
@@ -131,7 +131,7 @@ fn test_vectors() -> Result<()> {
}
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);
test_oprf_seed_to_key::<NistP256>(&p256_oprf_tvs)?;
test_oprf_blind::<NistP256>(&p256_oprf_tvs)?;
@@ -140,7 +140,7 @@ fn test_vectors() -> Result<()> {
test_oprf_evaluate::<NistP256>(&p256_oprf_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);
test_voprf_seed_to_key::<NistP256>(&p256_voprf_tvs)?;
test_voprf_blind::<NistP256>(&p256_voprf_tvs)?;
@@ -149,7 +149,7 @@ fn test_vectors() -> Result<()> {
test_voprf_evaluate::<NistP256>(&p256_voprf_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);
test_poprf_seed_to_key::<NistP256>(&p256_poprf_tvs)?;
test_poprf_blind::<NistP256>(&p256_poprf_tvs)?;
@@ -157,14 +157,64 @@ fn test_vectors() -> Result<()> {
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(())
}
fn test_oprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_oprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let server = OprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
@@ -176,11 +226,7 @@ where
Ok(())
}
fn test_voprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_voprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let server = VoprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
@@ -196,11 +242,7 @@ where
Ok(())
}
fn test_poprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_poprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let server = PoprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
@@ -217,11 +259,7 @@ where
}
// Tests input -> blind, blinded_element
fn test_oprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_oprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
@@ -242,11 +280,7 @@ where
}
// Tests input -> blind, blinded_element
fn test_voprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_voprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
@@ -267,11 +301,7 @@ where
}
// Tests input -> blind, blinded_element
fn test_poprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_poprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
@@ -292,11 +322,7 @@ where
}
// Tests sksm, blinded_element -> evaluation_element
fn test_oprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_oprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
@@ -313,13 +339,7 @@ where
Ok(())
}
fn test_voprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
fn test_voprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = VoprfServer::<CS>::new_with_key(&parameters.sksm)?;
@@ -345,13 +365,7 @@ where
Ok(())
}
fn test_poprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
fn test_poprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
@@ -387,11 +401,7 @@ where
}
// Tests input, blind, evaluation_element -> output
fn test_oprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_oprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let client =
@@ -408,11 +418,7 @@ where
Ok(())
}
fn test_voprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_voprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
@@ -447,11 +453,7 @@ where
Ok(())
}
fn test_poprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_poprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
@@ -485,11 +487,7 @@ where
}
// 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>,
{
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
@@ -502,11 +500,7 @@ where
Ok(())
}
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = VoprfServer::<CS>::new_with_key(&parameters.sksm)?;
@@ -519,11 +513,7 @@ where
Ok(())
}
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
+67 -127
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
// LICENSE-MIT file in the root directory of this source tree and the Apache
// 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 or the Apache
// 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
@@ -12,11 +13,10 @@ use alloc::vec::Vec;
use core::iter::{self, Map, Repeat, Zip};
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser};
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
use digest::{Digest, Output};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use rand_core::{TryCryptoRng, TryRngCore};
use crate::common::{
derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2,
@@ -39,13 +39,9 @@ use crate::{CipherSuite, Error, Group, Result};
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct VoprfClient<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct VoprfClient<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) blind: <CS::Group as Group>::Scalar,
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -59,13 +55,9 @@ where
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
serde(bound = "")
)]
pub struct VoprfServer<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct VoprfServer<CS: CipherSuite> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) sk: <CS::Group as Group>::Scalar,
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
@@ -77,21 +69,17 @@ where
// =================== //
/////////////////////////
impl<CS: CipherSuite> VoprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> VoprfClient<CS> {
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF.
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
pub fn blind<R: RngCore + CryptoRng>(
pub fn blind<R: TryRngCore + TryCryptoRng>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<VoprfClientBlindResult<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)
}
@@ -160,7 +148,7 @@ where
///
/// The resulting messages can each fail individually with [`Error::Input`]
/// if the `input` is empty or longer then [`u16::MAX`].
pub fn batch_finalize<'a, I: 'a, II, IC, IM>(
pub fn batch_finalize<'a, I, II, IC, IM>(
inputs: &'a II,
clients: &'a IC,
messages: &'a IM,
@@ -169,7 +157,7 @@ where
) -> Result<VoprfClientBatchFinalizeResult<'a, CS, I, II, IC, IM>>
where
CS: 'a,
I: AsRef<[u8]>,
I: 'a + AsRef<[u8]>,
&'a II: 'a + IntoIterator<Item = I>,
<&'a II as IntoIterator>::IntoIter: ExactSizeIterator,
&'a IC: 'a + IntoIterator<Item = &'a VoprfClient<CS>>,
@@ -196,25 +184,21 @@ where
}
}
// Only used for test functions
/// Only used for test functions
#[cfg(test)]
pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
self.blind
}
}
impl<CS: CipherSuite> VoprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
impl<CS: CipherSuite> VoprfServer<CS> {
/// Produces a new instance of a [VoprfServer] using a supplied RNG
///
/// # Errors
/// [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self> {
pub fn new<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self> {
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
rng.fill_bytes(&mut seed);
rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
// This can't fail as the hash output is type constrained.
Self::new_from_seed(&seed, &[])
}
@@ -245,7 +229,7 @@ where
Ok(Self { sk, pk })
}
// Only used for tests
/// Only used for tests
#[cfg(test)]
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
self.sk
@@ -254,7 +238,7 @@ where
/// Computes the second step for the multiplicative blinding version of
/// DH-OPRF. This message is sent from the server (who holds the OPRF key)
/// to the client.
pub fn blind_evaluate<R: RngCore + CryptoRng>(
pub fn blind_evaluate<R: TryRngCore + TryCryptoRng>(
&self,
rng: &mut R,
blinded_element: &BlindedElement<CS>,
@@ -287,7 +271,7 @@ where
/// [`Error::Batch`] if the number of `blinded_elements` and
/// `evaluation_elements` don't match or is longer then [`u16::MAX`]
#[cfg(feature = "alloc")]
pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, I>(
pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, I>(
&self,
rng: &mut R,
blinded_elements: &'a I,
@@ -338,7 +322,7 @@ where
pub fn batch_blind_evaluate_finish<
'a,
'b,
R: RngCore + CryptoRng,
R: TryRngCore + TryCryptoRng,
IB: Iterator<Item = &'a BlindedElement<CS>> + ExactSizeIterator,
IE,
>(
@@ -401,11 +385,7 @@ where
/// Contains the fields that are returned by a verifiable client blind
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
pub struct VoprfClientBlindResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct VoprfClientBlindResult<CS: CipherSuite> {
/// The state to be persisted on the client
pub state: VoprfClient<CS>,
/// The message to send to the server
@@ -422,11 +402,7 @@ pub type VoprfClientBatchFinalizeResult<'a, C, I, II, IC, IM> = FinalizeAfterUnb
/// Contains the fields that are returned by a verifiable server evaluate
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
pub struct VoprfServerEvaluateResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct VoprfServerEvaluateResult<CS: CipherSuite> {
/// The message to send to the client
pub message: EvaluationElement<CS>,
/// The proof for the client to verify
@@ -436,11 +412,7 @@ where
/// Contains the fields that are returned by a verifiable server batch evaluate
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
#[cfg(feature = "alloc")]
pub struct VoprfServerBatchEvaluateResult<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
pub struct VoprfServerBatchEvaluateResult<CS: CipherSuite> {
/// The messages to send to the client
pub messages: Vec<EvaluationElement<CS>>,
/// The proof for the client to verify
@@ -471,8 +443,6 @@ pub type VoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map<
#[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, <CS::Group as Group>::Scalar)]
pub struct VoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
&'a I: IntoIterator<Item = &'a PreparedEvaluationElement<CS>>,
{
/// The [`EvaluationElement`]s to send to the client
@@ -510,8 +480,6 @@ fn verifiable_unblind<'a, CS: 'a + CipherSuite, IC, IM>(
proof: &Proof<CS>,
) -> Result<VoprfUnblindResult<'a, CS, IC, IM>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
&'a IC: 'a + IntoIterator<Item = &'a VoprfClient<CS>>,
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
@@ -536,7 +504,7 @@ where
)?;
Ok(blinds
.zip(messages.into_iter())
.zip(messages)
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
}
@@ -553,11 +521,7 @@ fn finalize_after_unblind<
IE: 'a + Iterator<Item = (I, <CS::Group as Group>::Elem)>,
>(
inputs_and_unblinded_elements: IE,
) -> FinalizeAfterUnblindResult<'a, CS, I, IE>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
) -> FinalizeAfterUnblindResult<'a, CS, I, IE> {
inputs_and_unblinded_elements.map(|(input, unblinded_element)| {
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
@@ -582,31 +546,23 @@ where
#[cfg(test)]
mod tests {
use core::ops::Add;
use core::ptr;
use ::alloc::vec;
use ::alloc::vec::Vec;
use generic_array::sequence::Concat;
use generic_array::typenum::Sum;
use generic_array::ArrayLength;
use rand::rngs::OsRng;
use super::*;
use crate::common::{create_context_string, STR_HASH_TO_GROUP};
use crate::common::{Dst, STR_HASH_TO_GROUP};
use crate::Group;
fn prf<CS: CipherSuite>(
input: &[u8],
key: <CS::Group as Group>::Scalar,
mode: Mode,
) -> Output<CS::Hash>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap();
) -> Output<CS::Hash> {
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
let res = point * &key;
@@ -616,11 +572,7 @@ mod tests {
.unwrap()
}
fn verifiable_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_retrieval<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -639,11 +591,7 @@ mod tests {
assert_eq!(client_finalize_result, res2);
}
fn verifiable_batch_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_batch_retrieval<CS: CipherSuite>() {
let mut rng = OsRng;
let mut inputs = vec![];
let mut client_states = vec![];
@@ -651,7 +599,7 @@ mod tests {
let num_iterations = 10;
for _ in 0..num_iterations {
let mut input = [0u8; 32];
rng.fill_bytes(&mut input);
rng.try_fill_bytes(&mut input).unwrap();
let client_blind_result = VoprfClient::<CS>::blind(&input, &mut rng).unwrap();
inputs.push(input);
client_states.push(client_blind_result.state);
@@ -687,11 +635,7 @@ mod tests {
assert_eq!(client_finalize_result, res2);
}
fn verifiable_batch_bad_public_key<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_batch_bad_public_key<CS: CipherSuite>() {
let mut rng = OsRng;
let mut inputs = vec![];
let mut client_states = vec![];
@@ -699,7 +643,7 @@ mod tests {
let num_iterations = 10;
for _ in 0..num_iterations {
let mut input = [0u8; 32];
rng.fill_bytes(&mut input);
rng.try_fill_bytes(&mut input).unwrap();
let client_blind_result = VoprfClient::<CS>::blind(&input, &mut rng).unwrap();
inputs.push(input);
client_states.push(client_blind_result.state);
@@ -718,31 +662,25 @@ mod tests {
.unwrap();
let messages: Vec<_> = messages.collect();
let wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP)
.concat(create_context_string::<CS>(Mode::Oprf));
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
// 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 =
VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk);
assert!(client_finalize_result.is_err());
}
fn verifiable_bad_public_key<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
fn verifiable_bad_public_key<CS: CipherSuite>() {
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 wrong_pk = {
let dst = GenericArray::from(STR_HASH_TO_GROUP)
.concat(create_context_string::<CS>(Mode::Oprf));
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
// 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(
input,
@@ -753,11 +691,7 @@ mod tests {
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>,
{
fn verifiable_server_evaluate<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -786,13 +720,7 @@ mod tests {
assert!(client_finalize != server_evaluate);
}
fn zeroize_voprf_client<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
{
fn zeroize_voprf_client<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -806,15 +734,7 @@ mod tests {
assert!(message.serialize().iter().all(|&x| x == 0));
}
fn zeroize_voprf_server<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
fn zeroize_voprf_server<CS: CipherSuite>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VoprfClient::<CS>::blind(input, &mut rng).unwrap();
@@ -837,6 +757,8 @@ mod tests {
#[test]
fn test_functionality() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
#[cfg(feature = "ristretto255")]
{
@@ -861,6 +783,24 @@ mod tests {
zeroize_voprf_client::<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(())
}
}