Compare commits

...
40 Commits
Author SHA1 Message Date
Kevin LewiandGitHub 4646fe4ad0 Exposing the derive_key() function under the danger feature (#73) 2022-04-05 00:41:38 -07:00
Kevin LewiandGitHub f26f5d2c57 Publishing v0.4.0-pre.1 (#72) 2022-04-01 17:30:28 -07:00
Kevin LewiandGitHub dfa88efeca Updating actions/checkout and cache (#71) 2022-04-01 16:29:39 -07:00
Kevin LewiandGitHub 0b3544fc79 Fixing taplo formatting (#70) 2022-04-01 16:08:18 -07:00
daxpeddaandGitHub 1a61401272 General improvements (#65)
* Relax `hash_to_scalar` and `hash_to_group` bounds

* Rename `util` to `common` and shuffle some stuff around

* Don't generate unnecessary public key

* Simplify 'elliptic-curve` serializing element implementation

* Fix new Clippy 1.59 warnings

* Simplify `Ristretto255::random_scalar` implementation

* Update `derive-where`

* Fix panic during Ristretto255 deserialization

* Remove iteration during de/serialization
2022-04-01 12:18:32 -07:00
daxpeddaandGitHub a366a14125 Activate curve25519-dalek crate with ristretto255 crate feature (#63) 2022-02-15 20:39:32 -08:00
Kevin LewiandGitHub 8277383fc3 Updating test vectors and add missing check for poprf (#62) 2022-02-15 13:37:59 -08:00
daxpeddaandGitHub 1af4f470c3 Update to curve25519-dalek pre-release (#61) 2022-02-14 12:54:03 -08:00
f8e0600b22 Merging Version 09 changes into main (#60)
* Syncing new test vectors and base mode

* Working set of test vectors for VOPRF mode

* Adding POPRF

* POPRF test vectors in sync

* Address review (#59)

Co-authored-by: daxpedda <[email protected]>
2022-02-13 04:00:11 -08:00
daxpeddaandGitHub 9eee936140 Move to ZeroizeOnDrop (#54)
* Move from `Zeroize` to `ZeroizeOnDrop`

* Pin pre-release dependencies
2022-01-28 03:37:55 -08:00
daxpeddaandGitHub b59b359aa3 General improvements (#56)
* Apply Rust traits to all public types and other improvements

* Move methods into appropriate section

* Check for zero scalars

* Change element and scalar de/serialization from `GenericArray` to slice

* Customize `serde` serialization
2022-01-27 16:38:17 -08:00
daxpeddaandGitHub b01b8ed409 Document all Errors (#55)
* Document all `Error`s

* Fix inaccuracies
2022-01-24 20:55:02 -08:00
daxpeddaandGitHub 16e072dcd4 Group trait overhaul part 2 (#53)
* Rely on elliptic-curve for hash-to-curve and P-256 implementations

* Update MSRV

* Remove unnecessary `#[macro_use]`

* Re-introduce `CipherSuite`

* Provide types for length shortcuts

* Remove `SUITE_ID` from `Group`

* Blanket implementation for RustCrypto `Curve`s

* Remove the p256 crate feature

* Rename `ristretto_*` crate features to `ristretto-*` for consistency

* Remove unnecessary allowed Clippy lints

* Remove some unnecessary constraints
2022-01-21 13:52:09 -08:00
daxpeddaandGitHub 652fd1d1d0 Group trait overhaul (#52)
* Decouple element from `Group`

* Change `SUITE_ID` to `u16` and rework `get_context_string()`

* Rework scalar de-serialization

* Rename `Group` methods

- `random_nonzero_scalar` -> `random_scalar`
- `scalar_as_bytes` -> `serialize_scalar`
- `scalar_invert` -> `invert_scalar`

* Rework element de-serialization

* Rename and remove `Group` methods

`to_arr` -> `serialize_elem`
`base_point` -> `base_elem`
`is_identity` -> removed
`identity` -> `identity_elem`
`zero_scalar` -> hidden behind `cfg(test)`

* Sort `Group` methods

* Rework `expand_message_xmd` and remove utility

* Improve P256 `hash_to_scalar`
2022-01-18 03:34:28 -08:00
daxpeddaandGitHub e7675437e6 Embrace digest::Output (#51) 2022-01-03 15:45:05 -08:00
daxpeddaandGitHub d316ce4c8d batch_evaluate without alloc (#48)
* `no_alloc` alternative to `VerifiableServer::batch_evaluate()`

* Rename `PreparedT` to `PreparedTscalar`
2021-12-29 03:14:28 -05:00
daxpeddaandGitHub 1c5e965446 Reduce version requirement (#50) 2021-12-28 06:18:14 -05:00
daxpeddaandGitHub 55ef981a3f General Improvements (#47)
* Introduce `Result` shorthand, re-export and rename `InternalError`

* Re-export some public API relevant types

* Move `deserialize`

* Remove branch in `i2osp`

* Make `serialize` and `serialize_owned` methods

* Update p256
2021-12-25 16:54:27 -05:00
daxpeddaandGitHub b2f6d5eac8 Update to digest 0.10 (#36) 2021-12-23 15:58:00 -05:00
daxpeddaandGitHub 5228474f05 Remove custom Serde implementation (#44)
* Serialize `BlindedElement` as `GenericArray`

* Don't hold input

* Remove allocations from `VerifiableClient::batch_finalize`

* Remove all allocation from serialization

* Remove required `alloc` support.

* Fix accidental usage of 1.57 API

* Let `VerifiableClient::batch_finalize` return a concrete type

* Simplify de-serialization

* Remove custom Serde implementation
2021-12-23 15:03:38 -05:00
daxpeddaandGitHub 6669a0c4e6 Introduce ristretto255 crate feature (#35) 2021-12-23 01:50:48 -05:00
daxpeddaandGitHub 140f9e063d Configure Rustfmt and Taplo (#38)
* Configure rustfmt

* Add Taplo configuration and run in CI
2021-12-22 19:17:03 -05:00
daxpeddaandGitHub 4700d4b725 Add Dependabot (#37) 2021-12-21 14:17:13 -05:00
daxpeddaandGitHub b1b315f23c General improvements (#34)
* Minor improvements

* Fix `Debug` implementation

* Fix de-serialization

* Fix accidental usage of nightly

* Fix MSRV warning

* Replace macro with derive-where

* Add `rust-version` into `Cargo.toml`

* Move Serde trait implementation macro to `serialization` module

* Add ability to test without a Ristretto backend

* Improve docs

* Fix testing multiple backends together

* Implement `Ord` and `PartialOrd`

* `no_std` by default

* Remove unnecessary `doc_cfg`

* Remove dev-dependency on self

* Implement `Ord` and `PartialOrd` for `InternalError`

* Remove base64 encoding for serde

* Only take references

* Remove unnecessary qualifications from super-trait times
2021-12-21 14:17:02 -05:00
daxpeddaandGitHub 7613610859 Add cargo-audit to CI (#40) 2021-12-21 14:15:34 -05:00
Kevin LewiandGitHub d9dc5d0a13 Updating test vectors to sync with draft version 08 (#32) 2021-10-25 22:06:42 -07:00
Kevin LewiandGitHub 1b3837a789 Publishing v0.2 (#31) 2021-10-18 16:47:13 -07:00
Kevin LewiandGitHub 093a15f597 Adding deterministic_blind_unchecked functionality under danger feature (#30) 2021-10-18 13:24:15 -07:00
daxpeddaandGitHub 14830f1436 Remove allocations by serialize (#29)
* Changed `expand_message_xmd` input to `Iterator`

* Changed `hash_to_scalar` input to `Iterator`

* Fix rustfmt

* Add documentation for private helper types

* Fix documentation

* Improve `chain!()` syntax bias

* Move helper functions to `mod util`
2021-10-15 16:56:21 -07:00
Kevin LewiandGitHub 8457e8b900 Adding "danger" feature to expose underlying elements (#27)
* Adding internal feature to expose underlying elements

* Applying @daxpedda's comments and suggestions
2021-10-14 13:55:43 -07:00
daxpeddaandGitHub de91fafdb3 Group improvements (#28)
* Implement `ConstantTimeEq` for `Group` and `Group::Scalar`

* Changed `expand_message_xmd` `dst` parameter to `GenericArray<u8, D>`

* Remove allocation for `dst_prime` in `expand_message_xmd`

* Remove allocation for `msg_prime` and `b` in `expand_message_xmd`

* Remove unnecessary references in `expand_message_xmd`

* Return `GenericArray` from `expand_message_xmd`

* Oxidize loop in `expand_message_xmd`

* Remove allocation for `b` in `expand_message_xmd`

* Remove allocation for `xor`

* De-duplicate code in `expand_message_xmd`

* Remove unnecessary features

* Make ristretto optional and export all backends

* Make `rand` optional

* Fix wrong documentation

* Document `rand` feature

* Remove empty line

* Fix rustfmt

* Fix testing with other backends

* Iterate by value in `expand_message_xmd`

* Fix typo

* Rename `ristretto_x` features to `ristretto255_x`

* Remove `rand` re-export

* Improve `expand_message_xmd` loop

* Remove unnecessary `#[doc(cfg())]`

* Remove unnecessary `pub(crate)` for `mod p256`

* Fix `p256` only build

* Rename `serialize` feature to `serde`

* Fix missing `Cargo.toml` update
2021-10-14 11:09:00 -07:00
daxpeddaandGitHub 5ac52388ff Reduce allocations and some general code improvements (#26)
* Remove unnecessary paths

* Remove unnecessary conversion for `G::from_scalar_slice`

* Remove unnecessary allocations

* Return `GenericArray` from `get_context_string`

* Use `zip()` instead instead of indexing

* Changed `compute_composites` input to `Iterator`s

* Changed `generate_proof` input to `Iterator`s

* Changed `verify_proof` input to `Iterator`s

* Reduce allocations in `verifiable_unblind`

* Changed `verifiable_unblind` input to `Iterator`s

* Changed `batch_finalize` input to `Iterator`s
Removed `BatchFinalizeInput`

* Reduce allocations in `VerifiableClient::finalize`

* Changed `finalize_after_unblind` input to `Iterator`s

* Changed `batch_evaluate` input to `Iterator`s

* Reduce allocations in `evaluate`

* Changed `blind` input to `Into<Vec<u8>>`

* Remove note about `BatchFinalizeInput` from documentation

* Remove `Metadata`

* Changed `blind` input to `Vec<u8>`

* General improvements to test code

* Fix rustdoc

* Remove unnecessarily installed components
2021-10-12 15:07:47 -07:00
fab7528a69 Improvements (#24)
* Remove manual `Debug` impl for `InternalError`

* Remove unnecessary `#[macro_use]`

* Remove unnecessary  `tuple` handling in macro

* Improve serialization macro impl

* Improve `impl_traits_for` macro

* Re-direct `Drop` implementation

* Improve macro readability

* Fix rustdoc warnings

* Rust 1.51 has no support for `rustdoc` lints

* Change `i2osp` output to `GenericArray` from `Vec`

* Reduce calls to `to_vec()` and simplify conversion

* Adding zeroize tests

Co-authored-by: Kevin Lewi <[email protected]>
2021-10-10 17:51:12 -07:00
daxpeddaandGitHub c93600498e Add clippy::cargo and remove dependencies (#23) 2021-10-05 17:52:08 -07:00
daxpeddaandGitHub 6fb4cad59c Remove CipherSuite (#20)
* Remove `Hash`

* Remove `CipherSuite`

* Remove single field `struct`s
2021-10-05 15:53:18 -07:00
daxpeddaandGitHub 2d8780476a Use Clippy on test code too (#22) 2021-10-05 15:19:20 -07:00
daxpeddaandGitHub a3db6cd9d2 P256 improvements (#21)
* P256 improvements

* Fix Rust 1.51 compilation
2021-10-04 13:50:29 -07:00
daxpeddaandGitHub a1ab892bdb P256 improvements (#18) 2021-09-30 14:00:58 -07:00
daxpeddaandGitHub ad04f224af Improve documentation on docs.rs (#17) 2021-09-30 14:00:32 -07:00
daxpeddaandGitHub 7be67b26de Re-export simd_backend (#19) 2021-09-30 12:34:29 -07:00
32 changed files with 5193 additions and 3540 deletions
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
+12
View File
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
+84 -23
View File
@@ -7,24 +7,48 @@ on:
types: [opened, repoened, synchronize]
jobs:
cargo-audit:
name: Audit
runs-on: ubuntu-latest
steps:
- name: Cache cargo-audit
uses: actions/cache@v3
with:
path: |
~/.cargo/.crates.toml
~/.cargo/.crates2.json
~/.cargo/bin/cargo-audit
key: cargo-audit
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Checkout sources
uses: actions/checkout@v3
- name: Run cargo audit
run: cargo audit -D warnings
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- --features ristretto255-ciphersuite,ristretto255-u64
- --features ristretto255-ciphersuite,ristretto255-u32
-
frontend_feature:
- serialize
-
- --features danger
- --features serde
toolchain:
- stable
- 1.51.0
- 1.57.0
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
@@ -32,19 +56,24 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.backend_feature }}
args: --no-default-features ${{ matrix.backend_feature }}
- name: Run cargo test with alloc
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features ${{ matrix.frontend_feature }},alloc ${{ matrix.backend_feature }}
- name: Run cargo test with std
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.frontend_feature }},std --features ${{ matrix.backend_feature }}
args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }}
build-no-std:
name: Build with no-std on ${{ matrix.target }}
@@ -58,16 +87,18 @@ jobs:
# for any no_std target
- thumbv6m-none-eabi
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
-
- --features ristretto255-ciphersuite,ristretto255-u64
- --features ristretto255-ciphersuite,ristretto255-u32
frontend_feature:
- serialize
-
- --features danger
- --features serde
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- run: rustup target add ${{ matrix.target }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }} --features ${{ matrix.backend_feature }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }}
clippy:
@@ -75,7 +106,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
@@ -83,32 +114,62 @@ jobs:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
components: clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: --all-targets -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
env:
RUSTDOCFLAGS: -D warnings
with:
command: doc
args: --no-deps --document-private-items --features danger,std
format:
rustfmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install stable toolchain
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: nightly
override: true
components: rustfmt, clippy
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
taplo:
name: Taplo
runs-on: ubuntu-latest
steps:
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/.crates.toml
~/.cargo/.crates2.json
~/.cargo/bin/taplo
key: taplo
- name: Install Taplo
run: cargo install taplo-cli
- name: Checkout sources
uses: actions/checkout@v3
- name: Run Taplo
run: taplo fmt --check
+22
View File
@@ -1,5 +1,27 @@
# Changelog
## 0.4.0-pre.2 (April 5, 2022)
* Exposes the derive_key function under the "danger" feature
## 0.4.0-pre.1 (April 1, 2022)
* Updated to be in sync with draft-irtf-cfrg-voprf-09, with
the addition of the POPRF mode
* Added support for running the API without performing allocations
* Revamped the way the Group trait was used, so as to be more easily
extendable to other groups
* Added common traits for each public-facing struct, including serde
support
## 0.3.0 (October 25, 2021)
* Updated to be in sync with draft-irtf-cfrg-voprf-08
## 0.2.0 (October 18, 2021)
* Removed the CipherSuite interface
* Added the "danger" feature for exposing internal functions
* General improvements to the group interface
## 0.1.0 (September 29, 2021)
* Initial release
+45 -39
View File
@@ -1,56 +1,62 @@
[package]
name = "voprf"
version = "0.1.0"
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
authors = ["Kevin Lewi <[email protected]>"]
categories = ["no-std"]
categories = ["no-std", "algorithms", "cryptography"]
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
edition = "2021"
keywords = ["oprf"]
license = "MIT"
edition = "2018"
name = "voprf"
readme = "README.md"
resolver = "2"
repository = "https://github.com/novifinancial/voprf/"
rust-version = "1.57"
version = "0.4.0-pre.2"
[features]
default = ["u64_backend", "serialize"]
p256 = ["num-bigint", "num-integer", "num-traits", "once_cell", "p256_"]
std = ["curve25519-dalek/std", "getrandom", "rand/std", "rand/std_rng", "num-bigint/std", "num-integer/std", "num-traits/std"]
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
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_"]
std = ["alloc"]
[dependencies]
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
constant_time_eq = "0.1"
curve25519-dalek = { version = "3", default-features = false }
digest = "0.9"
curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true }
derive-where = { version = "=1.0.0-rc.3", features = ["zeroize-on-drop"] }
digest = "0.10"
displaydoc = { version = "0.2", default-features = false }
elliptic-curve = { version = "=0.12.0-pre.1", features = [
"hash2curve",
"sec1",
"voprf",
] }
generic-array = "0.14"
getrandom = { version = "0.2", optional = true }
hkdf = "0.11"
hmac = "0.11"
num-bigint = { version = "0.4", default-features = false, optional = true }
num-integer = { version = "0.1", default-features = false, optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
once_cell = { version = "1", default-features = false, optional = true }
p256_ = { package = "p256", version = "0.9", default-features = false, features = ["arithmetic", "zeroize"], optional = true }
rand = { version = "0.8", default-features = false }
serde = { version = "1", default-features = false, features = ["alloc", "derive"], optional = true }
rand_core = { version = "0.6", default-features = false }
serde_ = { version = "1", package = "serde", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", features = ["zeroize_derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"], optional = true }
zeroize = { version = "1.5", default-features = false }
[dev-dependencies]
base64 = "0.13"
bincode = "1"
chacha20poly1305 = "0.8"
criterion = "0.3"
generic-array = { version = "0.14", features = ["more_lengths"] }
hex = "0.4"
json = "0.12"
lazy_static = "1"
serde_json = "1"
sha2 = "0.9"
p256 = { version = "=0.11.0-pre.0", default-features = false, features = [
"hash2curve",
"voprf",
] }
proptest = "1"
rand = "0.8"
regex = "1"
rustyline = "8"
voprf = { path = "", default-features = false, features = ["std"] }
sha2 = "0.10"
[package.metadata.docs.rs]
features = ["danger", "std"]
targets = []
+2 -2
View File
@@ -16,12 +16,12 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.1.0"
voprf = "0.4.0-pre.2"
```
### Minimum Supported Rust Version
Rust **1.51** or higher.
Rust **1.57** or higher.
Contributors
------------
+8
View File
@@ -0,0 +1,8 @@
format_code_in_doc_comments = true
format_strings = true
group_imports = "StdExternalCrate"
imports_granularity = "Module"
license_template_path = ".cargo/license.rs"
newline_style = "Unix"
unstable_features = true
wrap_comments = true
+36 -5
View File
@@ -7,11 +7,42 @@
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
use digest::core_api::BlockSizeUser;
use digest::{Digest, OutputSizeUser};
use elliptic_curve::VoprfParameters;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use crate::Group;
/// Configures the underlying primitives used in VOPRF
pub trait CipherSuite {
pub trait CipherSuite
where
<Self::Hash as OutputSizeUser>::OutputSize:
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;
/// 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::Group`.
type Group: crate::group::Group;
/// The main hash function to use (for HKDF computations and hashing transcripts).
type Hash: crate::hash::Hash;
/// customization on how to hash an input to a curve point. See [`Group`].
type Group: Group;
/// The main hash function to use (for HKDF computations and hashing
/// transcripts).
type Hash: BlockSizeUser + Digest;
}
impl<T: VoprfParameters> CipherSuite for T
where
T: Group,
T::Hash: BlockSizeUser + Digest,
<T::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
{
const ID: u16 = T::ID;
type Group = T;
type Hash = T::Hash;
}
+473
View File
@@ -0,0 +1,473 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Common functionality between multiple OPRF modes.
use core::convert::TryFrom;
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, OutputSizeUser};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U11, U2, U256};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
#[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar};
use crate::{CipherSuite, Error, Group, InternalError, Result};
///////////////
// Constants //
// ========= //
///////////////
pub(crate) const STR_FINALIZE: [u8; 8] = *b"Finalize";
pub(crate) const STR_SEED: [u8; 5] = *b"Seed-";
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"VOPRF09-";
pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-";
pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-";
/// Determines the mode of operation (either base mode or verifiable mode). This
/// is only used for custom implementations for [`Group`].
#[derive(Clone, Copy, Debug)]
pub enum Mode {
/// Non-verifiable mode.
Oprf,
/// Verifiable mode.
Voprf,
/// Partially-oblivious mode.
Poprf,
}
impl Mode {
/// Mode as it is represented in a context string.
pub fn to_u8(self) -> u8 {
match self {
Mode::Oprf => 0,
Mode::Voprf => 1,
Mode::Poprf => 2,
}
}
}
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
/// The first client message sent from a client (either verifiable or not) to a
/// server (either verifiable or not).
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "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).
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "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.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "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>;
/// A proof produced by a server that the OPRF output matches against a server
/// public key.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
)]
pub struct Proof<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
#[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>"))]
pub(crate) s_scalar: <CS::Group as Group>::Scalar,
}
/////////////////////
// Proof Functions //
// =============== //
/////////////////////
/// Can only fail with [`Error::Batch`].
#[allow(clippy::many_single_char_names)]
pub(crate) fn generate_proof<CS: CipherSuite, R: RngCore + CryptoRng>(
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,
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-09.html#section-2.2.1
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
let r = CS::Group::random_scalar(rng);
let t2 = a * &r;
let t3 = m * &r;
// Bm = GG.SerializeElement(B)
let bm = CS::Group::serialize_elem(b);
// a0 = GG.SerializeElement(M)
let a0 = CS::Group::serialize_elem(m);
// a1 = GG.SerializeElement(Z)
let a1 = CS::Group::serialize_elem(z);
// a2 = GG.SerializeElement(t2)
let a2 = CS::Group::serialize_elem(t2);
// a3 = GG.SerializeElement(t3)
let a3 = CS::Group::serialize_elem(t3);
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
// h2Input = I2OSP(len(Bm), 2) || Bm ||
// I2OSP(len(a0), 2) || a0 ||
// I2OSP(len(a1), 2) || a1 ||
// I2OSP(len(a2), 2) || a2 ||
// I2OSP(len(a3), 2) || a3 ||
// "Challenge"
let h2_input = [
&elem_len,
bm.as_slice(),
&elem_len,
&a0,
&elem_len,
&a1,
&elem_len,
&a2,
&elem_len,
&a3,
&STR_CHALLENGE,
];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(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 s_scalar = r - &(c_scalar * &k);
Ok(Proof { c_scalar, s_scalar })
}
/// Can only fail with [`Error::ProofVerification`] or [`Error::Batch`].
#[allow(clippy::many_single_char_names)]
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,
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-09.html#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);
// Bm = GG.SerializeElement(B)
let bm = CS::Group::serialize_elem(b);
// a0 = GG.SerializeElement(M)
let a0 = CS::Group::serialize_elem(m);
// a1 = GG.SerializeElement(Z)
let a1 = CS::Group::serialize_elem(z);
// a2 = GG.SerializeElement(t2)
let a2 = CS::Group::serialize_elem(t2);
// a3 = GG.SerializeElement(t3)
let a3 = CS::Group::serialize_elem(t3);
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
// h2Input = I2OSP(len(Bm), 2) || Bm ||
// I2OSP(len(a0), 2) || a0 ||
// I2OSP(len(a1), 2) || a1 ||
// I2OSP(len(a2), 2) || a2 ||
// I2OSP(len(a3), 2) || a3 ||
// "Challenge"
let h2_input = [
&elem_len,
bm.as_slice(),
&elem_len,
&a0,
&elem_len,
&a1,
&elem_len,
&a2,
&elem_len,
&a3,
&STR_CHALLENGE,
];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(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();
match c.ct_eq(&proof.c_scalar).into() {
true => Ok(()),
false => Err(Error::ProofVerification),
}
}
type ComputeCompositesResult<CS> = (
<<CS as CipherSuite>::Group as Group>::Elem,
<<CS as CipherSuite>::Group as Group>::Elem,
);
/// Can only fail with [`Error::Batch`].
fn compute_composites<
CS: CipherSuite,
IC: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
ID: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
>(
k_option: Option<<CS::Group as Group>::Scalar>,
b: <CS::Group as Group>::Elem,
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-09.html#section-2.2.1
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
if c_slice.len() != d_slice.len() {
return Err(Error::Batch);
}
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));
// 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(CS::Group::serialize_elem(b))
.chain_update(i2osp_2_array(&seed_dst))
.chain_update(seed_dst)
.finalize();
let seed_len = i2osp_2_array(&seed);
let mut m = CS::Group::identity_elem();
let mut z = CS::Group::identity_elem();
for (i, (c, d)) in (0..len).zip(c_slice.zip(d_slice)) {
// Ci = GG.SerializeElement(Cs[i])
let ci = CS::Group::serialize_elem(c);
// Di = GG.SerializeElement(Ds[i])
let di = CS::Group::serialize_elem(d);
// h2Input = I2OSP(len(seed), 2) || seed || I2OSP(i, 2) ||
// I2OSP(len(Ci), 2) || Ci ||
// I2OSP(len(Di), 2) || Di ||
// "Composite"
let h2_input = [
seed_len.as_slice(),
&seed,
&i.to_be_bytes(),
&elem_len,
&ci,
&elem_len,
&di,
&STR_COMPOSITE,
];
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(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();
m = c * &di + &m;
z = match k_option {
Some(_) => z,
None => d * &di + &z,
};
}
z = match k_option {
Some(k) => m * &k,
None => z,
};
Ok((m, z))
}
/////////////////////
// Inner Functions //
// =============== //
/////////////////////
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
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);
let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;
for counter in 0_u8..=u8::MAX {
// deriveInput = seed || I2OSP(len(info), 2) || info
// skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair"
// || contextString)
let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
&[seed, &info_len, info, &counter.to_be_bytes()],
&dst,
)
.map_err(|_| Error::DeriveKeyPair)?;
if !bool::from(CS::Group::is_zero_scalar(sk_s)) {
return Ok(sk_s);
}
}
Err(Error::Protocol)
}
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
#[cfg(feature = "danger")]
pub fn derive_key<CS: CipherSuite>(
seed: &[u8],
info: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Scalar, Error>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
derive_key_internal::<CS>(seed, info, mode)
}
type DeriveKeypairResult<CS> = (
<<CS as CipherSuite>::Group as Group>::Scalar,
<<CS as CipherSuite>::Group as Group>::Elem,
);
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
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>,
{
let sk_s = derive_key_internal::<CS>(seed, info, mode)?;
let pk_s = CS::Group::base_elem() * &sk_s;
Ok((sk_s, pk_s))
}
/// Inner function for blind that assumes that the blinding factor has already
/// been chosen, and therefore takes it as input. Does not check if the blinding
/// factor is non-zero.
///
/// Can only fail with [`Error::Input`].
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>,
{
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
let hashed_point =
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).map_err(|_| Error::Input)?;
Ok(hashed_point * blind)
}
/// 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>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
GenericArray::from(STR_VOPRF)
.concat([mode.to_u8()].into())
.concat(CS::ID.to_be_bytes().into())
}
///////////////////////
// Utility Functions //
// ================= //
///////////////////////
pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
u16::try_from(input)
.map(|input| input.to_be_bytes())
.map_err(|_| InternalError::I2osp)
}
pub(crate) fn i2osp_2_array<L: ArrayLength<u8> + IsLess<U256>>(
_: &GenericArray<u8, L>,
) -> GenericArray<u8, U2> {
L::U16.to_be_bytes().into()
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! 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)]
pub enum Error {
/// Size of info is longer then [`u16::MAX`].
Info,
/// Size of input is empty or longer then [`u16::MAX`].
Input,
/// Size of info and seed together are longer then `u16::MAX - 3`.
DeriveKeyPair,
/// Failure to deserialize bytes
Deserialization,
/// Batched items are more then [`u16::MAX`] or length don't match.
Batch,
/// In verifiable mode, occurs when the proof failed to verify
ProofVerification,
/// The protocol has failed and can't be completed.
Protocol,
}
/// Only used to implement [`Group`](crate::Group).
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InternalError {
/// Size of input is empty or longer then [`u16::MAX`].
Input,
/// `input` is longer then [`u16::MAX`].
I2osp,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
-60
View File
@@ -1,60 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! A list of error types which are produced during an execution of the protocol
use core::fmt::Debug;
#[cfg(feature = "std")]
use std::error::Error;
use displaydoc::Display;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Clone, Display, Eq, Hash, PartialEq)]
pub enum InternalError {
/// Could not parse byte sequence for key
InvalidByteSequence,
/// Could not deserialize element, or deserialized to the identity element
PointError,
/// Computing the hash-to-curve function failed
HashToCurveError,
/// Failure to serialize or deserialize bytes
SerializationError,
/// Use of incompatible modes (base vs. verifiable)
IncompatibleModeError,
/**
* Internal error thrown when different-lengthed slices are supplied
* to the compute_composites() function.
*/
MismatchedLengthsForCompositeInputs,
/// In verifiable mode, occurs when the proof failed to verify
ProofVerificationError,
/// Encountered insufficient bytes when attempting to deserialize
SizeError,
/// Encountered a zero scalar
ZeroScalarError,
}
impl Debug for InternalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidByteSequence => f.debug_tuple("InvalidByteSequence").finish(),
Self::PointError => f.debug_tuple("PointError").finish(),
Self::HashToCurveError => f.debug_tuple("HashToCurveError").finish(),
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
Self::IncompatibleModeError => f.debug_tuple("IncompatibleModeError").finish(),
Self::MismatchedLengthsForCompositeInputs => f
.debug_tuple("MismatchedLengthsForCompositeInputs")
.finish(),
Self::ProofVerificationError => f.debug_tuple("ProofVerificationError").finish(),
Self::SizeError => f.debug_tuple("SizeError").finish(),
Self::ZeroScalarError => f.debug_tuple("ZeroScalarError").finish(),
}
}
}
#[cfg(feature = "std")]
impl Error for InternalError {}
+107
View File
@@ -0,0 +1,107 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use digest::core_api::BlockSizeUser;
use digest::Digest;
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,
};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use super::Group;
use crate::{Error, InternalError, Result};
impl<C> Group for C
where
C: GroupDigest,
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
FieldSize<Self>: ModulusSize,
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
Scalar<Self>: FromOkm,
{
type Elem = ProjectivePoint<Self>;
type ElemLen = <FieldSize<Self> as ModulusSize>::CompressedPointSize;
type Scalar = Scalar<Self>;
type ScalarLen = FieldSize<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>
where
H: Digest + BlockSizeUser,
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>
where
H: Digest + BlockSizeUser,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
<Self as GroupDigest>::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
.map_err(|_| InternalError::Input)
}
fn base_elem() -> Self::Elem {
ProjectivePoint::<Self>::generator()
}
fn identity_elem() -> Self::Elem {
ProjectivePoint::<Self>::identity()
}
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
let bytes = elem.to_encoded_point(true);
let bytes = bytes.as_bytes();
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(bytes);
result
}
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
PublicKey::<Self>::from_sec1_bytes(element_bits)
.map(|public_key| public_key.to_projective())
.map_err(|_| Error::Deserialization)
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
*SecretKey::<Self>::random(rng).to_nonzero_scalar()
}
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
Option::from(scalar.invert()).unwrap()
}
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
scalar.is_zero()
}
#[cfg(test)]
fn zero_scalar() -> Self::Scalar {
Scalar::<Self>::zero()
}
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.into()
}
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
SecretKey::<Self>::from_be_bytes(scalar_bits)
.map(|secret_key| *secret_key.to_nonzero_scalar())
.map_err(|_| Error::Deserialization)
}
}
-192
View File
@@ -1,192 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use crate::errors::InternalError;
use crate::hash::Hash;
use crate::serialization::i2osp;
use alloc::vec::Vec;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalError> {
if x.len() != y.len() {
return Err(InternalError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
}
/// Corresponds to the expand_message_xmd() function defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
pub fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalError> {
let b_in_bytes = <H as Digest>::OutputSize::USIZE;
let r_in_bytes = <H as BlockInput>::BlockSize::USIZE;
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalError::HashToCurveError);
}
let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat();
let z_pad = i2osp(0, r_in_bytes)?;
let l_i_b_str = i2osp(len_in_bytes, 2)?;
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat();
let mut b: Vec<Vec<u8>> = alloc::vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
}
#[cfg(test)]
mod tests {
struct Params {
msg: &'static str,
len_in_bytes: usize,
uniform_bytes: &'static str,
}
#[test]
fn test_expand_message_xmd() {
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
let test_vectors: alloc::vec::Vec<Params> = alloc::vec![
Params {
msg: "",
len_in_bytes: 0x20,
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
92181df928fca88",
},
Params {
msg: "abc",
len_in_bytes: 0x20,
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
5a1151775fe4da7",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x20,
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x20,
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
1716b1b964e1c642",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x20,
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
350db46f429b771b",
},
Params {
msg: "",
len_in_bytes: 0x80,
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
},
Params {
msg: "abc",
len_in_bytes: 0x80,
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x80,
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x80,
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x80,
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+81 -90
View File
@@ -7,122 +7,113 @@
//! Defines the Group trait to specify the underlying prime order group
mod expand;
#[cfg(feature = "p256")]
pub(crate) mod p256;
mod elliptic_curve;
#[cfg(feature = "ristretto255")]
mod ristretto;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::ops::{Add, Mul, Sub};
use digest::core_api::BlockSizeUser;
use digest::Digest;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use rand_core::{CryptoRng, RngCore};
#[cfg(feature = "ristretto255")]
pub use ristretto::Ristretto255;
use subtle::{Choice, ConstantTimeEq};
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:
Copy
+ Sized
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
+ for<'a> Add<&'a Self, Output = Self>
{
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn hash_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError>;
/// The type of base field scalars
type Scalar: Zeroize
pub trait Group {
/// The type of group elements
type Elem: ConstantTimeEq
+ Copy
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;
/// Return a scalar from its fixed-length bytes representation, without
/// checking if the scalar is zero.
fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalError>;
/// Return a scalar from its fixed-length bytes representation. If the scalar
/// is zero, then return an error.
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalError> {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits)?;
if Self::ct_equal_scalar(&scalar, &Self::scalar_zero()) {
return Err(InternalError::ZeroScalarError);
}
Ok(scalar)
}
/// picks a scalar at random
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
+ Zeroize
+ for<'a> Add<&'a Self::Elem, Output = Self::Elem>
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8> + 'static;
/// Return an element from its fixed-length bytes representation. This is
/// the unchecked version, which does not check for deserializing the identity
/// element
fn from_element_slice_unchecked(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalError>;
/// The type of base field scalars
type Scalar: ConstantTimeEq
+ Copy
+ Zeroize
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>;
/// Return an element from its fixed-length bytes representation. If the element
/// is the identity element, return an error.
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalError> {
let elem = Self::from_element_slice_unchecked(element_bits)?;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;
if Self::ct_equal(&elem, &<Self as Group>::identity()) {
// found the identity element
return Err(InternalError::PointError);
}
/// 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>
where
H: Digest + BlockSizeUser,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
Ok(elem)
/// Hashes a slice of pseudo-random bytes to a scalar
///
/// # 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>
where
H: Digest + BlockSizeUser,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
/// Get the base point for the group
fn base_elem() -> Self::Elem;
/// Returns the identity group element
fn identity_elem() -> Self::Elem;
/// Returns `true` if the element is equal to the identity element
fn is_identity_elem(elem: Self::Elem) -> Choice {
Self::identity_elem().ct_eq(&elem)
}
/// Serializes the `self` group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen>;
/// Get the base point for the group
fn base_point() -> Self;
/// Return an element from its fixed-length bytes representation. If the
/// element is the identity element, return an error.
///
/// # Errors
/// [`Error::Deserialization`](crate::Error::Deserialization) if the element
/// is not a valid point on the group or the identity element.
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem>;
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self.ct_equal(&<Self as Group>::identity())
}
/// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Returns the identity group element
fn identity() -> Self;
/// The multiplicative inverse of this scalar
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar;
/// Returns `true` if the scalar is zero.
fn is_zero_scalar(scalar: Self::Scalar) -> Choice;
/// Returns the scalar representing zero
fn scalar_zero() -> Self::Scalar;
#[cfg(test)]
fn zero_scalar() -> Self::Scalar;
/// Compares in constant time if the group elements are equal
fn ct_equal(&self, other: &Self) -> bool;
/// Serializes a scalar to bytes
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
/// Compares in constant time if the scalars are equal
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool;
/// Set the contents of self to the identity value
fn zeroize(&mut self) {
*self = <Self as Group>::identity();
}
/// Return a scalar from its fixed-length bytes representation. If the
/// scalar is zero or invalid, then return an error.
///
/// # Errors
/// [`Error::Deserialization`](crate::Error::Deserialization) if the scalar
/// is not a valid point on the group or zero.
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar>;
}
#[cfg(test)]
-555
View File
@@ -1,555 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// Note: This group implementation of p256 is experimental for now,
// until hash-to-curve or crypto-bigint are fully supported.
#![allow(
clippy::borrow_interior_mutable_const,
clippy::declare_interior_mutable_const
)]
use super::Group;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::str::FromStr;
use generic_array::typenum::{U32, U33};
use generic_array::{ArrayLength, GenericArray};
use num_bigint::{BigInt, Sign};
use num_integer::Integer;
use num_traits::{One, ToPrimitive, Zero};
use once_cell::unsync::Lazy;
use p256_::elliptic_curve::group::prime::PrimeCurveAffine;
use p256_::elliptic_curve::group::GroupEncoding;
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use p256_::elliptic_curve::subtle::ConstantTimeEq;
use p256_::elliptic_curve::Field;
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
use rand::{CryptoRng, RngCore};
// `L: 48`
pub const L: usize = 48;
impl Group for ProjectivePoint {
const SUITE_ID: usize = 0x0003;
// 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: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `p: 2^256 - 2^224 + 2^192 + 2^96 - 1`
const P: Lazy<BigInt> = Lazy::new(|| {
BigInt::from_str(
"115792089210356248762697446949407573530086143415290314195533631308867097853951",
)
.unwrap()
});
// `A: -3`
const A: Lazy<BigInt> = Lazy::new(|| BigInt::from(-3));
// `B: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`
const B: Lazy<BigInt> = Lazy::new(|| {
BigInt::parse_bytes(
b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
16,
)
.unwrap()
});
// `Z: -10`
const Z: Lazy<BigInt> = Lazy::new(|| BigInt::from(-10));
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
// `hash_to_curve` calls `hash_to_field` with a `count` of `2`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// `hash_to_field` calls `expand_message` with a `len_in_bytes` of `count * L`
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 2 * L)?;
// hash to curve
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L..], &A, &B, &P, &Z);
// convert to `p256` types
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
&q0x, &q0y, false,
))
.ok_or(InternalError::PointError)?
.to_curve();
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
&q1x, &q1y, false,
))
.ok_or(InternalError::PointError)?;
Ok(p0 + p1)
}
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#[{%22num%22:211,%22gen%22:0},{%22name%22:%22XYZ%22},70,700,0]
// P-256 `n` is defined as `115792089210356248762697446949407573529996955224135760342 422259061068512044369`
const N: once_cell::unsync::Lazy<BigInt> = once_cell::unsync::Lazy::new(|| {
BigInt::from_str(
"115792089210356248762697446949407573529996955224135760342422259061068512044369",
)
.unwrap()
});
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// `HashToScalar` is `hash_to_field`
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, L)?;
let mut bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
.mod_floor(&N)
.to_bytes_be()
.1;
bytes.resize(32, 0);
Ok(p256_::Scalar::from_bytes_reduced(GenericArray::from_slice(
&bytes,
)))
}
type ElemLen = U33;
type Scalar = p256_::Scalar;
type ScalarLen = U32;
fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalError> {
Ok(Self::Scalar::from_bytes_reduced(scalar_bits))
}
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Self::Scalar::random(rng)
}
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.into()
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert().unwrap_or(Self::Scalar::zero())
}
fn from_element_slice_unchecked(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalError> {
Option::from(Self::from_bytes(element_bits)).ok_or(InternalError::PointError)
}
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let mut bytes = self.to_affine().to_encoded_point(true).as_bytes().to_vec();
bytes.resize(33, 0);
*GenericArray::from_slice(&bytes)
}
fn base_point() -> Self {
Self::generator()
}
fn identity() -> Self {
Self::identity()
}
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
s1.ct_eq(s2).into()
}
}
/// Corresponds to the hash_to_curve_simple_swu() function defined in
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-F.2>
///
/// `cmov`, `mod_floor` and `modpow` needs to be made constant-time, which
/// will be supported after crypto-bigint is no longer experimental. See
/// https://github.com/novifinancial/voprf/issues/13 for more context.
#[allow(clippy::many_single_char_names)]
fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
u: &[u8],
a: &BigInt,
b: &BigInt,
p: &BigInt,
z: &BigInt,
) -> (GenericArray<u8, N>, GenericArray<u8, N>) {
#[derive(Clone)]
struct Field<'a>(&'a BigInt);
impl<'a> Field<'a> {
fn new(p: &'a BigInt) -> Self {
Self(p)
}
fn element(&'a self, number: &BigInt) -> FieldElement<'a> {
FieldElement {
number: number.mod_floor(self.0),
f: self,
}
}
fn one(&'a self) -> FieldElement<'a> {
self.element(&BigInt::one())
}
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
fn inv0(&'a self, number: &FieldElement<'a>) -> FieldElement<'a> {
number.pow_internal(&(self.0 - 2))
}
}
/// Finite field arithmetic
#[derive(Clone)]
struct FieldElement<'a> {
number: BigInt,
f: &'a Field<'a>,
}
impl<'a> Add for FieldElement<'a> {
type Output = FieldElement<'a>;
fn add(self, rhs: Self) -> Self::Output {
&self + &rhs
}
}
impl<'a> Add for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn add(self, rhs: Self) -> Self::Output {
self.f.element(&(&self.number + &rhs.number))
}
}
impl<'a> Sub for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn sub(self, rhs: Self) -> Self::Output {
self.f.element(&(&self.number - &rhs.number))
}
}
impl<'a> Neg for FieldElement<'a> {
type Output = FieldElement<'a>;
fn neg(self) -> Self::Output {
-&self
}
}
impl<'a> Neg for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn neg(self) -> Self::Output {
self.f.element(&-&self.number)
}
}
impl<'a> Mul for FieldElement<'a> {
type Output = FieldElement<'a>;
fn mul(self, rhs: Self) -> Self::Output {
&self * &rhs
}
}
impl<'a> Mul<&Self> for FieldElement<'a> {
type Output = FieldElement<'a>;
fn mul(self, rhs: &Self) -> Self::Output {
&self * rhs
}
}
impl<'a> Mul<FieldElement<'a>> for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn mul(self, rhs: FieldElement<'a>) -> Self::Output {
self * &rhs
}
}
impl<'a> Mul for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn mul(self, rhs: Self) -> Self::Output {
self.f.element(&(&self.number * &rhs.number))
}
}
impl<'a> Div<&Self> for FieldElement<'a> {
type Output = FieldElement<'a>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, rhs: &Self) -> Self::Output {
self * rhs.f.inv0(rhs)
}
}
impl<'a> FieldElement<'a> {
fn square(&self) -> Self {
self * self
}
fn pow_internal(&self, exponent: &BigInt) -> Self {
let exponent = exponent.mod_floor(&(self.f.0 - 1));
self.f.element(&self.number.modpow(&exponent, self.f.0))
}
/// Corresponds to the sqrt_3mod4() function defined in
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-I.1>
fn sqrt(&self) -> Self {
// constant
let c1 = (self.f.0 + 1) >> 2;
self.pow_internal(&c1)
}
/// Corresponds to the sgn0_m_eq_1() function defined in
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4.1>
fn sgn0(&self) -> i32 {
(&self.number % 2_usize).to_i32().unwrap()
}
fn is_zero(&self) -> bool {
self.number.is_zero()
}
/// Corresponds to the is_square() function defined in
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
fn is_square(&self) -> bool {
// constant
let exponent = (self.f.0 - 1) >> 1;
let result = self.pow_internal(&exponent);
result.is_zero() || result.number.is_one()
}
fn to_bytes<N: ArrayLength<u8>>(&self) -> GenericArray<u8, N> {
let val = self.number.mod_floor(self.f.0).to_bytes_be().1;
let mut bytes = alloc::vec![0u8; 32 - val.len()];
bytes.extend_from_slice(&val);
GenericArray::clone_from_slice(&bytes)
}
}
fn cmov<'a>(x: &FieldElement<'a>, y: &FieldElement<'a>, b: bool) -> FieldElement<'a> {
if b {
y.clone()
} else {
x.clone()
}
}
let f = Field::new(p);
let a = f.element(a);
let b = f.element(b);
let z = f.element(z);
let u = f.element(&BigInt::from_bytes_be(Sign::Plus, u));
// Constants:
// 1. c1 = -B / A
let c1 = -&b / &a;
// 2. c2 = -1 / Z
let c2 = -f.one() / &z;
// Steps:
// 1. tv1 = Z * u^2
let tv1 = z * u.square();
// 2. tv2 = tv1^2
let mut tv2 = tv1.square();
// 3. x1 = tv1 + tv2
let mut x1 = &tv1 + &tv2;
// 4. x1 = inv0(x1)
x1 = f.inv0(&x1);
// 5. e1 = x1 == 0
let e1 = x1.is_zero();
// 6. x1 = x1 + 1
x1 = x1 + f.one();
// 7. x1 = CMOV(x1, c2, e1) # If (tv1 + tv2) == 0, set x1 = -1 / Z
x1 = cmov(&x1, &c2, e1);
// 8. x1 = x1 * c1 # x1 = (-B / A) * (1 + (1 / (Z^2 * u^4 + Z * u^2)))
x1 = x1 * c1;
// 9. gx1 = x1^2
let mut gx1 = x1.square();
// 10. gx1 = gx1 + A
gx1 = gx1 + a;
// 11. gx1 = gx1 * x1
gx1 = gx1 * &x1;
// 12. gx1 = gx1 + B # gx1 = g(x1) = x1^3 + A * x1 + B
gx1 = gx1 + b;
// 13. x2 = tv1 * x1 # x2 = Z * u^2 * x1
let x2 = &tv1 * &x1;
// 14. tv2 = tv1 * tv2
tv2 = tv1 * tv2;
// 15. gx2 = gx1 * tv2 # gx2 = (Z * u^2)^3 * gx1
let gx2 = &gx1 * tv2;
// 16. e2 = is_square(gx1)
let e2 = gx1.is_square();
// 17. x = CMOV(x2, x1, e2) # If is_square(gx1), x = x1, else x = x2
let x = cmov(&x2, &x1, e2);
// 18. y2 = CMOV(gx2, gx1, e2) # If is_square(gx1), y2 = gx1, else y2 = gx2
let y2 = cmov(&gx2, &gx1, e2);
// 19. y = sqrt(y2)
let mut y = y2.sqrt();
// 20. e3 = sgn0(u) == sgn0(y) # Fix sign of y
let e3 = u.sgn0() == y.sgn0();
// 21. y = CMOV(-y, y, e3)
y = cmov(&-&y, &y, e3);
// 22. return (x, y)
(x.to_bytes(), y.to_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
struct Params {
msg: &'static str,
px: &'static str,
py: &'static str,
u0: &'static str,
u1: &'static str,
q0x: &'static str,
q0y: &'static str,
q1x: &'static str,
q1y: &'static str,
}
#[test]
fn hash_to_curve_simple_swu() {
const P: Lazy<BigInt> = Lazy::new(|| {
BigInt::from_str(
"115792089210356248762697446949407573530086143415290314195533631308867097853951",
)
.unwrap()
});
const A: Lazy<BigInt> = Lazy::new(|| BigInt::from(-3));
const B: Lazy<BigInt> = Lazy::new(|| {
BigInt::parse_bytes(
b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
16,
)
.unwrap()
});
const Z: Lazy<BigInt> = Lazy::new(|| BigInt::from(-10));
// Test vectors taken from https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.1.1
let test_vectors = alloc::vec![
Params {
msg: "",
px: "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4",
py: "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415",
u0: "ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009",
u1: "8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a",
q0x: "ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5",
q0y: "dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1",
q1x: "51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5",
q1y: "b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac",
},
Params {
msg: "abc",
px: "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f",
py: "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e",
u0: "afe47f2ea2b10465cc26ac403194dfb68b7f5ee865cda61e9f3e07a537220af1",
u1: "379a27833b0bfe6f7bdca08e1e83c760bf9a338ab335542704edcd69ce9e46e0",
q0x: "5219ad0ddef3cc49b714145e91b2f7de6ce0a7a7dc7406c7726c7e373c58cb48",
q0y: "7950144e52d30acbec7b624c203b1996c99617d0b61c2442354301b191d93ecf",
q1x: "019b7cb4efcfeaf39f738fe638e31d375ad6837f58a852d032ff60c69ee3875f",
q1y: "589a62d2b22357fed5449bc38065b760095ebe6aeac84b01156ee4252715446e",
},
Params {
msg: "abcdef0123456789",
px: "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80",
py: "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3",
u0: "0fad9d125a9477d55cf9357105b0eb3a5c4259809bf87180aa01d651f53d312c",
u1: "b68597377392cd3419d8fcc7d7660948c8403b19ea78bbca4b133c9d2196c0fb",
q0x: "a17bdf2965eb88074bc01157e644ed409dac97cfcf0c61c998ed0fa45e79e4a2",
q0y: "4f1bc80c70d411a3cc1d67aeae6e726f0f311639fee560c7f5a664554e3c9c2e",
q1x: "7da48bb67225c1a17d452c983798113f47e438e4202219dd0715f8419b274d66",
q1y: "b765696b2913e36db3016c47edb99e24b1da30e761a8a3215dc0ec4d8f96e6f9",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
px: "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d",
py: "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e",
u0: "3bbc30446f39a7befad080f4d5f32ed116b9534626993d2cc5033f6f8d805919",
u1: "76bb02db019ca9d3c1e02f0c17f8baf617bbdae5c393a81d9ce11e3be1bf1d33",
q0x: "c76aaa823aeadeb3f356909cb08f97eee46ecb157c1f56699b5efebddf0e6398",
q0y: "776a6f45f528a0e8d289a4be12c4fab80762386ec644abf2bffb9b627e4352b1",
q1x: "418ac3d85a5ccc4ea8dec14f750a3a9ec8b85176c95a7022f391826794eb5a75",
q1y: "fd6604f69e9d9d2b74b072d14ea13050db72c932815523305cb9e807cc900aff",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
px: "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5",
py: "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc",
u0: "4ebc95a6e839b1ae3c63b847798e85cb3c12d3817ec6ebc10af6ee51adb29fec",
u1: "4e21af88e22ea80156aff790750121035b3eefaa96b425a8716e0d20b4e269ee",
q0x: "d88b989ee9d1295df413d4456c5c850b8b2fb0f5402cc5c4c7e815412e926db8",
q0y: "bb4a1edeff506cf16def96afff41b16fc74f6dbd55c2210e5b8f011ba32f4f40",
q1x: "a281e34e628f3a4d2a53fa87ff973537d68ad4fbc28d3be5e8d9f6a2571c5a4b",
q1y: "f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184",
},
];
let dst = "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
for tv in test_vectors {
let uniform_bytes = super::super::expand::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
96,
)
.unwrap();
let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[..48]).mod_floor(&P);
let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[48..]).mod_floor(&P);
assert_eq!(BigInt::parse_bytes(tv.u0.as_bytes(), 16).unwrap(), u0);
assert_eq!(BigInt::parse_bytes(tv.u1.as_bytes(), 16).unwrap(), u1);
let (q0x, q0y) = super::hash_to_curve_simple_swu(&u0.to_bytes_be().1, &A, &B, &P, &Z);
let (q1x, q1y) = super::hash_to_curve_simple_swu(&u1.to_bytes_be().1, &A, &B, &P, &Z);
assert_eq!(tv.q0x, hex::encode(q0x));
assert_eq!(tv.q0y, hex::encode(q0y));
assert_eq!(tv.q1x, hex::encode(q1x));
assert_eq!(tv.q1y, hex::encode(q1y));
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
&q0x, &q0y, false,
))
.unwrap()
.to_curve();
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
&q1x, &q1y, false,
))
.unwrap();
let p = (p0 + p1).to_encoded_point(false);
assert_eq!(tv.px, hex::encode(p.x().unwrap()));
assert_eq!(tv.py, hex::encode(p.y().unwrap()));
}
}
}
+97 -85
View File
@@ -5,76 +5,104 @@
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use super::Group;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::convert::TryInto;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use generic_array::{typenum::U32, GenericArray};
use rand::{CryptoRng, RngCore};
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 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 subtle::ConstantTimeEq;
/// The implementation of such a subgroup for Ristretto
impl Group for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
use super::Group;
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;
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;
type ElemLen = U32;
type Scalar = Scalar;
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: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 64)?;
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
where
H: Digest + BlockSizeUser,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let mut uniform_bytes = GenericArray::<_, U64>::default();
ExpandMsgXmd::<H>::expand_message(input, dst, 64)
.map_err(|_| InternalError::Input)?
.fill_bytes(&mut uniform_bytes);
Ok(RistrettoPoint::from_uniform_bytes(
uniform_bytes
.as_slice()
.try_into()
.map_err(|_| InternalError::HashToCurveError)?,
))
Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes.into()))
}
// 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: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, 64)?;
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError>
where
H: Digest + BlockSizeUser,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let mut uniform_bytes = GenericArray::<_, U64>::default();
ExpandMsgXmd::<H>::expand_message(input, dst, 64)
.map_err(|_| InternalError::Input)?
.fill_bytes(&mut uniform_bytes);
Ok(Scalar::from_bytes_mod_order_wide(
uniform_bytes
.as_slice()
.try_into()
.map_err(|_| InternalError::HashToCurveError)?,
))
Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes.into()))
}
type Scalar = Scalar;
type ScalarLen = U32;
fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalError> {
Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref()))
fn base_elem() -> Self::Elem {
RISTRETTO_BASEPOINT_POINT
}
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
fn identity_elem() -> Self::Elem {
RistrettoPoint::identity()
}
// serialization of a group element
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
elem.compress().to_bytes().into()
}
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
if element_bits.len() != 32 {
return Err(Error::Deserialization);
}
CompressedRistretto::from_slice(element_bits)
.decompress()
.filter(|point| point != &RistrettoPoint::identity())
.ok_or(Error::Deserialization)
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
};
let scalar = Scalar::random(rng);
if scalar != Scalar::zero() {
break scalar;
@@ -82,45 +110,29 @@ impl Group for RistrettoPoint {
}
}
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.to_bytes().into()
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice_unchecked(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or(InternalError::PointError)
}
// serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
self.compress().to_bytes().into()
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
scalar.ct_eq(&Scalar::zero())
}
fn base_point() -> Self {
RISTRETTO_BASEPOINT_POINT
#[cfg(test)]
fn zero_scalar() -> Self::Scalar {
Scalar::zero()
}
fn identity() -> Self {
<Self as Identity>::identity()
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.to_bytes().into()
}
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
ConstantTimeEq::ct_eq(self, other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
ConstantTimeEq::ct_eq(s1, s2).into()
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
scalar_bits
.try_into()
.ok()
.and_then(Scalar::from_canonical_bytes)
.filter(|scalar| scalar != &Scalar::zero())
.ok_or(Error::Deserialization)
}
}
+20 -28
View File
@@ -7,51 +7,43 @@
//! Includes a series of tests for the group implementations
use crate::errors::InternalError;
use crate::group::Group;
use crate::CipherSuite;
use crate::{Error, Group, Result};
// Test that the deserialization of a group element should throw an error
// if the identity element can be deserialized properly
// Test that the deserialization of a group element should throw an error if the
// identity element can be deserialized properly
#[test]
fn test_group_properties() -> Result<(), InternalError> {
use crate::tests::Ristretto255Sha512;
fn test_group_properties() -> Result<()> {
use p256::NistP256;
test_identity_element_error::<Ristretto255Sha512>()?;
test_zero_scalar_error::<Ristretto255Sha512>()?;
#[cfg(feature = "p256")]
#[cfg(feature = "ristretto255")]
{
use crate::tests::P256Sha256;
use crate::Ristretto255;
test_identity_element_error::<P256Sha256>()?;
test_zero_scalar_error::<P256Sha256>()?;
test_identity_element_error::<Ristretto255>()?;
test_zero_scalar_error::<Ristretto255>()?;
}
test_identity_element_error::<NistP256>()?;
test_zero_scalar_error::<NistP256>()?;
Ok(())
}
// Checks that the identity element cannot be deserialized
fn test_identity_element_error<CS: CipherSuite>() -> Result<(), InternalError> {
let identity = CS::Group::identity();
let result = CS::Group::from_element_slice(&identity.to_arr());
assert!(match result {
Err(InternalError::PointError) => true,
_ => false,
});
fn test_identity_element_error<G: Group>() -> Result<()> {
let identity = G::identity_elem();
let result = G::deserialize_elem(&G::serialize_elem(identity));
assert!(matches!(result, Err(Error::Deserialization)));
Ok(())
}
// Checks that the zero scalar cannot be deserialized
fn test_zero_scalar_error<CS: CipherSuite>() -> Result<(), InternalError> {
let zero_scalar = CS::Group::scalar_zero();
let result = CS::Group::from_scalar_slice(&CS::Group::scalar_as_bytes(zero_scalar));
assert!(match result {
Err(InternalError::ZeroScalarError) => true,
_ => false,
});
fn test_zero_scalar_error<G: Group>() -> Result<()> {
let zero_scalar = G::zero_scalar();
let result = G::deserialize_scalar(&G::serialize_scalar(zero_scalar));
assert!(matches!(result, Err(Error::Deserialization)));
Ok(())
}
-17
View File
@@ -1,17 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! A convenience trait for digest bounds used throughout the library
use digest::{BlockInput, FixedOutput, Reset, Update};
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
-203
View File
@@ -1,203 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
macro_rules! impl_debug_eq_hash_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: core::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("$name")
.field("$field1", &self.$field1)
$(.field("$field2", &self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: core::hash::Hash,)+)?
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.$field1, state);
$(core::hash::Hash::hash(&self.$field2, state);)*
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: core::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("$name")
.field(&self.$field1)
$(.field(&self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: core::hash::Hash,)+)?
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.$field1, state);
$(core::hash::Hash::hash(&self.$field2, state);)*
}
}
};
}
macro_rules! impl_clone_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
}
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self(
self.$field1.clone(),
$(self.$field2.clone(),)*
)
}
}
};
}
macro_rules! impl_zeroize_on_drop_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)?
{
fn zeroize(&mut self) {
self.$field1.zeroize();
$(self.$field2.zeroize();)*
}
}
impl$(<$($gen$(: $bound)?),+>)? Drop for $name$(<$($gen),+>)?
{
fn drop(&mut self) {
#[allow(unused_imports)]
use zeroize::Zeroize;
self.$field1.zeroize();
$(self.$field2.zeroize();)*
}
}
};
}
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
} else {
struct ByteVisitor<CS: CipherSuite> {
marker: core::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut core::fmt::Formatter,
) -> core::fmt::Result {
formatter.write_str(core::concat!(
"the byte representation of a ",
core::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&core::concat!(
"invalid byte sequence for ",
core::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: core::marker::PhantomData,
})
}
}
}
};
}
// Convenience macro for implementing all of the above traits
macro_rules! impl_traits_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl_debug_eq_hash_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_clone_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_zeroize_on_drop_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_serialize_and_deserialize_for!($name);
}
}
+317 -288
View File
@@ -8,310 +8,265 @@
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
//!
//! Note: This implementation is in sync with
//! [draft-irtf-cfrg-voprf-07](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html),
//! [draft-irtf-cfrg-voprf-09](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html),
//! but this specification is subject to change, until the final version
//! published by the IETF.
//!
//! # Overview
//!
//! A verifiable oblivious pseudorandom function is a protocol that is
//! evaluated between a client and a server. They must first agree on a
//! collection of primitives to be kept consistent throughout protocol
//! execution. These include:
//! - a finite cyclic group along with a point representation, and
//! - a hashing function.
//! A verifiable oblivious pseudorandom function is a protocol that is evaluated
//! between a client and a server. They must first agree on a finite cyclic
//! group along with a point representation.
//!
//! We will use the following choices in this example:
//! We will use the following choice in this example:
//!
//! ```
//! use voprf::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! type Hash = sha2::Sha512;
//! }
//! ```ignore
//! type CipherSuite = voprf::Ristretto255;
//! ```
//!
//! ## Modes of Operation
//!
//! VOPRF can be used in two modes:
//! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation with no
//! support for the verification of the OPRF outputs
//! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF evaluation where
//! the outputs can be verified against a server public key
//! VOPRF can be used in three modes:
//! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation
//! with no support for the verification of the OPRF outputs
//! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF
//! evaluation where the outputs can be verified against a server public key
//! (VOPRF)
//! - [Partially Oblivious Verifiable Mode](#metadata), which corresponds to a
//! VOPRF, where a public input can be supplied to the PRF computation
//!
//! In either mode, the protocol begins with a client blinding, followed by
//! a server evaluation, and finishes with a client finalization.
//! In all of these modes, the protocol begins with a client blinding, followed
//! by a server evaluation, and finishes with a client finalization.
//!
//! ## Base Mode
//!
//! In base mode, a [NonVerifiableClient] interacts with a
//! [NonVerifiableServer] to compute the output of the VOPRF.
//! In base mode, an [OprfClient] interacts with an [OprfServer]
//! to compute the output of the OPRF.
//!
//! ### Server Setup
//!
//! The protocol begins with a setup phase, in which the server must run
//! [NonVerifiableServer::new()] to produce an instance of itself. This
//! instance must be persisted on the server and used for online
//! client evaluations.
//! [OprfServer::new()] to produce an instance of itself. This instance
//! must be persisted on the server and used for online client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! use voprf::NonVerifiableServer;
//! use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//! use voprf::OprfServer;
//!
//! let mut server_rng = OsRng;
//! let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! .expect("Unable to construct server");
//! let server = OprfServer::<CipherSuite>::new(&mut server_rng);
//! ```
//!
//! ### Client Blinding
//!
//! In the first step, the client chooses an input, and runs
//! [NonVerifiableClient::blind] to produce a [NonVerifiableClientBlindResult],
//! which consists of a [BlindedElement] to be sent to the server and a
//! [NonVerifiableClient] which must be persisted on the client for the final
//! [OprfClient::blind] to produce an [OprfClientBlindResult],
//! which consists of a [BlindedElement] to be sent to the server and an
//! [OprfClient] which must be persisted on the client for the final
//! step of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! use voprf::NonVerifiableClient;
//! use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//! use voprf::OprfClient;
//!
//! let mut client_rng = OsRng;
//! let client_blind_result = NonVerifiableClient::<Default>::blind(
//! b"input",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! let client_blind_result = OprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
//! .expect("Unable to construct client");
//! ```
//!
//! ### Server Evaluation
//!
//! In the second step, the server takes as input the message from
//! [NonVerifiableClient::blind] (a [BlindedElement]), and runs
//! [NonVerifiableServer::evaluate] to produce a
//! [NonVerifiableServerEvaluateResult], which consists of an
//! [EvaluationElement] to be sent to the client.
//! [OprfClient::blind] (a [BlindedElement]), and runs
//! [OprfServer::evaluate] to produce [EvaluationElement] to be sent to
//! the client.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::NonVerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # use voprf::OprfServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! use voprf::Metadata;
//! let server_evaluate_result = server.evaluate(
//! client_blind_result.message,
//! &Metadata::none(),
//! ).expect("Unable to perform server evaluate");
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let server_evaluate_result = server.evaluate(&client_blind_result.message);
//! ```
//!
//! ### Client Finalization
//!
//! In the final step, the client takes as input the message from
//! [NonVerifiableServer::evaluate] (an [EvaluationElement]), and runs
//! [NonVerifiableClient::finalize] to produce a
//! [NonVerifiableClientFinalizeResult], which consists of an
//! output for the protocol.
//! [OprfServer::evaluate] (an [EvaluationElement]), and runs
//! [OprfClient::finalize] to produce an output for the protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::NonVerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # use voprf::OprfServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # client_blind_result.message,
//! # &Metadata::none(),
//! # ).expect("Unable to perform server evaluate");
//! use voprf::Metadata;
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! &Metadata::none(),
//! ).expect("Unable to perform client finalization");
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let message = server.evaluate(&client_blind_result.message);
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(b"input", &message)
//! .expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ```
//!
//! ## Verifiable Mode
//!
//! In verifiable mode, a [VerifiableClient] interacts with a
//! [VerifiableServer] to compute the output of the VOPRF. In order to
//! verify the server's computation, the client checks a server-generated
//! proof against the server's public key. If the proof fails to verify,
//! then the client does not receive an output.
//! In verifiable mode, a [VoprfClient] interacts with a [VoprfServer]
//! to compute the output of the VOPRF. In order to verify the server's
//! computation, the client checks a server-generated proof against the server's
//! public key. If the proof fails to verify, then the client does not receive
//! an output.
//!
//! In batch mode, a single proof can be used for multiple VOPRF evaluations.
//! See [the batching section](#batching)
//! for more details on how to perform batch evaluations.
//! See [the batching section](#batching) for more details on how to perform
//! batch evaluations.
//!
//! ### Server Setup
//!
//! The protocol begins with a setup phase, in which the server must run
//! [VerifiableServer::new()] to produce an instance of itself. This
//! instance must be persisted on the server and used for online
//! client evaluations.
//! [VoprfServer::new()] to produce an instance of itself. This instance
//! must be persisted on the server and used for online client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! use voprf::VerifiableServer;
//! use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//! use voprf::VoprfServer;
//!
//! let mut server_rng = OsRng;
//! let server = VerifiableServer::<Default>::new(&mut server_rng)
//! .expect("Unable to construct server");
//! let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//!
//! // To be sent to the client
//! println!("Server public key: {:?}", server.get_public_key());
//! ```
//!
//! The public key should be sent to the client, since the client will
//! need it in the final step of the protocol in order to complete
//! the evaluation of the VOPRF.
//! The public key should be sent to the client, since the client will need it
//! in the final step of the protocol in order to complete the evaluation of the
//! VOPRF.
//!
//! ### Client Blinding
//!
//! In the first step, the client chooses an input, and runs
//! [VerifiableClient::blind] to produce a [VerifiableClientBlindResult],
//! which consists of a [BlindedElement] to be sent to the server and a
//! [VerifiableClient] which must be persisted on the client for the final
//! step of the VOPRF protocol.
//! [VoprfClient::blind] to produce a [VoprfClientBlindResult], which
//! consists of a [BlindedElement] to be sent to the server and a
//! [VoprfClient] which must be persisted on the client for the final step
//! of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! use voprf::VerifiableClient;
//! use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//! use voprf::VoprfClient;
//!
//! let mut client_rng = OsRng;
//! let client_blind_result = VerifiableClient::<Default>::blind(
//! b"input",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! let client_blind_result = VoprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
//! .expect("Unable to construct client");
//! ```
//!
//! ### Server Evaluation
//!
//! In the second step, the server takes as input the message from
//! [VerifiableClient::blind] (a [BlindedElement]), and runs
//! [VerifiableServer::evaluate] to produce a
//! [VerifiableServerEvaluateResult], which consists of an
//! [EvaluationElement] to be sent to the client along with a proof.
//! [VoprfClient::blind] (a [BlindedElement]), and runs
//! [VoprfServer::evaluate] to produce a [VoprfServerEvaluateResult],
//! which consists of an [EvaluationElement] to be sent to the client along with
//! a proof.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerEvaluateResult, VoprfClient};
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! use voprf::Metadata;
//! let server_evaluate_result = server.evaluate(
//! &mut server_rng,
//! client_blind_result.message,
//! &Metadata::none(),
//! ).expect("Unable to perform server evaluate");
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerEvaluateResult { message, proof } =
//! server.evaluate(&mut server_rng, &client_blind_result.message);
//! ```
//!
//! ### Client Finalization
//!
//! In the final step, the client takes as input the message from
//! [VerifiableServer::evaluate] (an [EvaluationElement]),
//! the proof, and the server's public key, and runs
//! [VerifiableClient::finalize] to produce a
//! [VerifiableClientFinalizeResult], which consists of an
//! [VoprfServer::evaluate] (an [EvaluationElement]), the proof, and the
//! server's public key, and runs [VoprfClient::finalize] to produce an
//! output for the protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let server_evaluate_result = server.evaluate(
//! # &mut server_rng,
//! # client_blind_result.message,
//! # &Metadata::none(),
//! # ).expect("Unable to perform server evaluate");
//! use voprf::Metadata;
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! server_evaluate_result.proof,
//! server.get_public_key(),
//! &Metadata::none(),
//! ).expect("Unable to perform client finalization");
//! # &client_blind_result.message,
//! # );
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(
//! b"input",
//! &server_evaluate_result.message,
//! &server_evaluate_result.proof,
//! server.get_public_key(),
//! )
//! .expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ```
//!
//! # Advanced Usage
@@ -322,167 +277,221 @@
//!
//! ## Batching
//!
//! It is sometimes desirable to generate only a single, constant-size
//! proof for an unbounded number of VOPRF evaluations (on arbitrary inputs).
//! [VerifiableClient] and [VerifiableServer] support a batch API for
//! handling this case. In the following example, we show how to use
//! the batch API to produce a single proof for 10 parallel
//! VOPRF evaluations.
//! It is sometimes desirable to generate only a single, constant-size proof for
//! an unbounded number of VOPRF evaluations (on arbitrary inputs).
//! [VoprfClient] and [VoprfServer] support a batch API for handling
//! this case. In the following example, we show how to use the batch API to
//! produce a single proof for 10 parallel VOPRF evaluations.
//!
//! First, the client produces 10 blindings, storing their resulting
//! states and messages:
//! First, the client produces 10 blindings, storing their resulting states and
//! messages:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! let mut client_rng = OsRng;
//! let mut client_states = vec![];
//! let mut client_messages = vec![];
//! for _ in 0..10 {
//! let client_blind_result = VerifiableClient::<Default>::blind(
//! b"input",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! let client_blind_result = VoprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
//! .expect("Unable to construct client");
//! client_states.push(client_blind_result.state);
//! client_messages.push(client_blind_result.message);
//! }
//! ```
//!
//! Next, the server calls the [VerifiableServer::batch_evaluate]
//! function on a set of client messages, to produce a corresponding
//! set of messages to be returned to the client (returned in the same order),
//! along with a single proof:
//! Next, the server calls the [VoprfServer::batch_evaluate_prepare] and
//! [VoprfServer::batch_evaluate_finish] function on a set of client
//! messages, to produce a corresponding set of messages to be returned to the
//! client (returned in the same order), along with a single proof:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerBatchEvaluateFinishResult, VoprfClient};
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # client_states.push(client_blind_result.state);
//! # client_messages.push(client_blind_result.message);
//! # }
//! # use voprf::Metadata;
//! # use voprf::VerifiableServer;
//! # use voprf::VoprfServer;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! let server_batch_evaluate_result = server.batch_evaluate(
//! &mut server_rng,
//! &client_messages,
//! &Metadata::none(),
//! ).expect("Unable to perform server batch evaluate");
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let prepared_evaluation_elements = server.batch_evaluate_prepare(client_messages.iter());
//! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
//! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
//! .batch_evaluate_finish(&mut server_rng, client_messages.iter(), &prepared_elements)
//! .expect("Unable to perform server batch evaluate");
//! let messages: Vec<_> = messages.collect();
//! ```
//!
//! Then, the client calls [VerifiableClient::batch_finalize] on
//! the client states saved from the first step, along with the messages
//! returned by the server (constructing a [BatchFinalizeInput]), along with the
//! server's proof, in order to produce a vector of outputs if the proof
//! verifies correctly.
//! If `alloc` is available, `VoprfServer::batch_evaluate` can be called
//! to avoid having to collect output manually:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # #[cfg(feature = "alloc")] {
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient};
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # client_states.push(client_blind_result.state);
//! # client_messages.push(client_blind_result.message);
//! # }
//! # use voprf::Metadata;
//! # use voprf::VerifiableServer;
//! use voprf::BatchFinalizeInput;
//! # use voprf::VoprfServer;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_batch_evaluate_result = server.batch_evaluate(
//! # &mut server_rng,
//! # &client_messages,
//! # &Metadata::none(),
//! # ).expect("Unable to perform server batch evaluate");
//! let batch_finalize_input = BatchFinalizeInput::new(
//! client_states,
//! server_batch_evaluate_result.messages,
//! );
//! let client_batch_finalize_result = VerifiableClient::batch_finalize(
//! batch_finalize_input,
//! server_batch_evaluate_result.proof,
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerBatchEvaluateResult { messages, proof } = server
//! .batch_evaluate(&mut server_rng, &client_messages)
//! .expect("Unable to perform server batch evaluate");
//! # }
//! ```
//!
//! Then, the client calls [VoprfClient::batch_finalize] on the client
//! states saved from the first step, along with the messages returned by the
//! server, along with the server's proof, in order to produce a vector of
//! outputs if the proof verifies correctly.
//!
//! ```
//! # #[cfg(feature = "alloc")] {
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient};
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # client_states.push(client_blind_result.state);
//! # client_messages.push(client_blind_result.message);
//! # }
//! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let VoprfServerBatchEvaluateResult { messages, proof } = server
//! # .batch_evaluate(&mut server_rng, &client_messages)
//! # .expect("Unable to perform server batch evaluate");
//! let client_batch_finalize_result = VoprfClient::batch_finalize(
//! &[b"input"; 10],
//! &client_states,
//! &messages,
//! &proof,
//! server.get_public_key(),
//! &Metadata::none(),
//! ).expect("Unable to perform client batch finalization");
//! )
//! .expect("Unable to perform client batch finalization")
//! .collect::<Vec<_>>();
//!
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result.outputs);
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result);
//! # }
//! ```
//!
//! ## Metadata
//!
//! The optional metadata parameter included in the protocol allows clients and
//! servers (of either mode) to cryptographically bind additional data to the
//! VOPRF output. This metadata is known to both parties at the start of the protocol,
//! and is inserted under the server's evaluate step and the client's finalize step.
//! This metadata can be constructed with some type of higher-level domain separation
//! to avoid cross-protocol attacks or related issues.
//! The optional metadata parameter included in the POPRF mode allows clients
//! and servers to cryptographically bind additional data to the
//! VOPRF output. This metadata is known to both parties at the start of the
//! protocol, and is inserted under the server's evaluate step and the client's
//! finalize step. This metadata can be constructed with some type of
//! higher-level domain separation to avoid cross-protocol attacks or related
//! issues.
//!
//! The default metadata simply consists of the empty vector of bytes, but a custom
//! metadata can be specified, for example, by: `Metadata(b"custom metadata")`.
//! The API for POPRF mode is similar to VOPRF mode, except that a [PoprfServer]
//! and [PoprfClient] are used, and that each of the functions accept an
//! additional (and optional) info parameter which represents the public input.
//! See <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#name-poprf-public-input>
//! for more detailed information on how this public input should be used.
//!
//! # Features
//!
//! - The `p256` feature enables using p256 as the underlying group for the [CipherSuite] choice.
//! Note that this is currently an experimental feature ⚠️, and is not yet ready for production use.
//! - The `alloc` feature requires Rust's `alloc` crate and enables batching
//! VOPRF evaluations.
//!
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
//! [serde](https://serde.rs/).
//! - The `serde` feature, enabled by default, provides convenience functions
//! for serializing and deserializing with [serde](https://serde.rs/).
//!
//! - The `u32_backend` and `u64_backend` features are re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and allow for selecting
//! the corresponding backend for the curve arithmetic used. The `u64_backend` feature is included as the default.
//! - The `danger` feature, disabled by default, exposes functions for setting
//! and getting internal values not available in the default API. These
//! functions are intended for use in by higher-level cryptographic protocols
//! that need access to these raw values and are able to perform the necessary
//! validations on them (such as being valid group elements).
//!
//! - The `ristretto255-ciphersuite` features enables using [`Ristretto255`] as
//! 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.
//!
//! [curve25519-dalek]: (https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features)
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
#![deny(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(test), deny(unsafe_code))]
#![no_std]
#![warn(
clippy::cargo,
clippy::missing_errors_doc,
missing_debug_implementations,
missing_docs
)]
#![allow(clippy::multiple_crate_versions)]
#[cfg(any(feature = "alloc", test))]
extern crate alloc;
#[macro_use]
mod impls;
#[macro_use]
mod serialization;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "serde")]
extern crate serde_ as serde;
mod ciphersuite;
pub mod errors;
pub mod group;
pub mod hash;
mod common;
mod error;
mod group;
mod oprf;
mod poprf;
mod serialization;
mod voprf;
#[cfg(test)]
@@ -490,12 +499,32 @@ mod tests;
// Exports
pub use rand;
pub use crate::ciphersuite::CipherSuite;
pub use crate::voprf::{
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
NonVerifiableClientBlindResult, NonVerifiableClientFinalizeResult, NonVerifiableServer,
NonVerifiableServerEvaluateResult, VerifiableClient, VerifiableClientBlindResult,
VerifiableClientFinalizeResult, VerifiableServer, VerifiableServerEvaluateResult,
#[cfg(feature = "danger")]
pub use crate::common::derive_key;
pub use crate::common::{
BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof,
};
pub use crate::error::{Error, InternalError, Result};
pub use crate::group::Group;
#[cfg(feature = "ristretto255")]
pub use crate::group::Ristretto255;
pub use crate::oprf::{OprfClient, OprfClientBlindResult, OprfServer};
#[cfg(feature = "alloc")]
pub use crate::poprf::PoprfServerBatchEvaluateResult;
pub use crate::poprf::{
PoprfClient, PoprfClientBatchFinalizeResult, PoprfPreparedTweak, PoprfServer,
PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluateFinishedMessages,
PoprfServerBatchEvaluatePrepareResult, PoprfServerBatchEvaluatePreparedEvaluationElements,
};
pub use crate::serialization::{
BlindedElementLen, EvaluationElementLen, OprfClientLen, OprfServerLen, PoprfClientLen,
PoprfServerLen, ProofLen, VoprfClientLen, VoprfServerLen,
};
#[cfg(feature = "alloc")]
pub use crate::voprf::VoprfServerBatchEvaluateResult;
pub use crate::voprf::{
VoprfClient, VoprfClientBatchFinalizeResult, VoprfClientBlindResult, VoprfServer,
VoprfServerBatchEvaluateFinishResult, VoprfServerBatchEvaluateFinishedMessages,
VoprfServerBatchEvaluatePreparedEvaluationElements, VoprfServerEvaluateResult,
};
+406
View File
@@ -0,0 +1,406 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! 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 generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use crate::common::{
derive_key_internal, deterministic_blind_unchecked, i2osp_2, BlindedElement, EvaluationElement,
Mode, STR_FINALIZE,
};
#[cfg(feature = "serde")]
use crate::serialization::serde::Scalar;
use crate::{CipherSuite, Error, Group, Result};
///////////////
// Constants //
// ========= //
///////////////
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
/// A client which engages with a [OprfServer] in base mode, meaning
/// that the OPRF outputs are not verifiable.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
)]
pub struct OprfClient<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) blind: <CS::Group as Group>::Scalar,
}
/// A server which engages with a [OprfClient] in base mode, meaning
/// that the OPRF outputs are not verifiable.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
)]
pub struct OprfServer<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) sk: <CS::Group as Group>::Scalar,
}
/////////////////////////
// API Implementations //
// =================== //
/////////////////////////
impl<CS: CipherSuite> OprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// 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>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<OprfClientBlindResult<CS>> {
let blind = CS::Group::random_scalar(blinding_factor_rng);
Self::deterministic_blind_unchecked_inner(input, blind)
}
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF, taking a blinding factor scalar as input instead of sampling
/// from an RNG.
///
/// # Caution
///
/// This should be used with caution, since it does not perform any checks
/// on the validity of the blinding factor!
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
#[cfg(any(feature = "danger", test))]
pub fn deterministic_blind_unchecked(
input: &[u8],
blind: <CS::Group as Group>::Scalar,
) -> Result<OprfClientBlindResult<CS>> {
Self::deterministic_blind_unchecked_inner(input, blind)
}
/// Can only fail with [`Error::Input`].
fn deterministic_blind_unchecked_inner(
input: &[u8],
blind: <CS::Group as Group>::Scalar,
) -> Result<OprfClientBlindResult<CS>> {
let blinded_element = deterministic_blind_unchecked::<CS>(input, &blind, Mode::Oprf)?;
Ok(OprfClientBlindResult {
state: Self { blind },
message: BlindedElement(blinded_element),
})
}
/// Computes the third step for the multiplicative blinding version of
/// DH-OPRF, in which the client unblinds the server's message.
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
pub fn finalize(
&self,
input: &[u8],
evaluation_element: &EvaluationElement<CS>,
) -> Result<Output<CS::Hash>> {
let unblinded_element = evaluation_element.0 * &CS::Group::invert_scalar(self.blind);
let mut outputs =
finalize_after_unblind::<CS, _, _>(iter::once((input, unblinded_element)), &[]);
outputs.next().unwrap()
}
/// Only used for test functions
#[cfg(test)]
pub fn from_blind(blind: <CS::Group as Group>::Scalar) -> Self {
Self { blind }
}
/// Exposes the blind group element
#[cfg(feature = "danger")]
pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
self.blind
}
}
impl<CS: CipherSuite> OprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// 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> {
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
rng.fill_bytes(&mut seed);
Self::new_from_seed(&seed, &[])
}
/// Produces a new instance of a [OprfServer] using a supplied set
/// of bytes to represent the server's private key
///
/// # Errors
/// [`Error::Deserialization`] if the private key is not a valid point on
/// the group or zero.
pub fn new_with_key(private_key_bytes: &[u8]) -> Result<Self> {
let sk = CS::Group::deserialize_scalar(private_key_bytes)?;
Ok(Self { sk })
}
/// Produces a new instance of a [OprfServer] using a supplied set
/// of bytes which are used as a seed to derive the server's private key.
///
/// 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.
pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
let sk = derive_key_internal::<CS>(seed, info, Mode::Oprf)?;
Ok(Self { sk })
}
// Only used for tests
#[cfg(test)]
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
self.sk
}
/// 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 evaluate(&self, blinded_element: &BlindedElement<CS>) -> EvaluationElement<CS> {
EvaluationElement(blinded_element.0 * &self.sk)
}
}
/////////////////////////
// Convenience Structs //
//==================== //
/////////////////////////
/// 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>,
{
/// The state to be persisted on the client
pub state: OprfClient<CS>,
/// The message to send to the server
pub message: BlindedElement<CS>,
}
/////////////////////
// Inner functions //
// =============== //
/////////////////////
type FinalizeAfterUnblindResult<'a, C, I, IE> = Map<
IE,
fn((I, <<C as CipherSuite>::Group as Group>::Elem)) -> Result<Output<<C as CipherSuite>::Hash>>,
>;
/// Returned values can only fail with [`Error::Input`].
fn finalize_after_unblind<
'a,
CS: CipherSuite,
I: AsRef<[u8]>,
IE: 'a + Iterator<Item = (I, <CS::Group as Group>::Elem)>,
>(
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>,
{
inputs_and_unblinded_elements.map(|(input, unblinded_element)| {
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(unblindedElement), 2) || unblindedElement ||
// "Finalize"
// return Hash(hashInput)
Ok(CS::Hash::new()
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(input.as_ref())
.chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(unblinded_element))
.chain_update(&STR_FINALIZE)
.finalize())
})
}
///////////
// Tests //
// ===== //
///////////
#[cfg(test)]
mod tests {
use core::ptr;
use generic_array::sequence::Concat;
use rand::rngs::OsRng;
use super::*;
use crate::common::{create_context_string, STR_HASH_TO_GROUP};
use crate::Group;
fn prf<CS: CipherSuite>(
input: &[u8],
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();
let res = point * &key;
finalize_after_unblind::<CS, _, _>(iter::once((input, res)), info)
.next()
.unwrap()
.unwrap()
}
fn base_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap();
let message = server.evaluate(&client_blind_result.message);
let client_finalize_result = client_blind_result.state.finalize(input, &message).unwrap();
let res2 = prf::<CS>(input, server.get_private_key(), &[], Mode::Oprf);
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>,
{
let mut rng = OsRng;
let mut input = [0u8; 64];
rng.fill_bytes(&mut input);
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 res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[])
.next()
.unwrap()
.unwrap();
assert_eq!(client_finalize_result, res2);
}
fn zeroize_oprf_client<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let mut state = client_blind_result.state;
unsafe { ptr::drop_in_place(&mut state) };
assert!(state.serialize().iter().all(|&x| x == 0));
let mut message = client_blind_result.message;
unsafe { ptr::drop_in_place(&mut message) };
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>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap();
let mut message = server.evaluate(&client_blind_result.message);
let mut state = server;
unsafe { ptr::drop_in_place(&mut state) };
assert!(state.serialize().iter().all(|&x| x == 0));
unsafe { ptr::drop_in_place(&mut message) };
assert!(message.serialize().iter().all(|&x| x == 0));
}
#[test]
fn test_functionality() -> Result<()> {
use p256::NistP256;
#[cfg(feature = "ristretto255")]
{
use crate::Ristretto255;
base_retrieval::<Ristretto255>();
base_inversion_unsalted::<Ristretto255>();
zeroize_oprf_client::<Ristretto255>();
zeroize_oprf_server::<Ristretto255>();
}
base_retrieval::<NistP256>();
base_inversion_unsalted::<NistP256>();
zeroize_oprf_client::<NistP256>();
zeroize_oprf_server::<NistP256>();
Ok(())
}
}
+932
View File
@@ -0,0 +1,932 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Contains the main POPRF API
#[cfg(feature = "alloc")]
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 crate::common::{
create_context_string, derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2,
verify_proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof,
STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO,
};
#[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar};
use crate::{CipherSuite, Error, Group, Result};
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
/// A client which engages with a [PoprfServer] in verifiable mode, meaning
/// that the OPRF outputs can be checked against a server public key.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
)]
pub struct PoprfClient<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
#[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>"))]
pub(crate) blinded_element: <CS::Group as Group>::Elem,
}
/// A server which engages with a [PoprfClient] in verifiable mode, meaning
/// that the OPRF outputs can be checked against a server public key.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "serde", bound = "")
)]
pub struct PoprfServer<CS: CipherSuite>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
#[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>"))]
pub(crate) pk: <CS::Group as Group>::Elem,
}
/////////////////////////
// API Implementations //
// =================== //
/////////////////////////
impl<CS: CipherSuite> PoprfClient<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// 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>(
blinding_factor_rng: &mut R,
input: &[u8],
) -> Result<PoprfClientBlindResult<CS>> {
let blind = CS::Group::random_scalar(blinding_factor_rng);
Self::deterministic_blind_unchecked_inner(input, blind)
}
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF, taking a blinding factor scalar as input instead of sampling
/// from an RNG.
///
/// # Caution
///
/// This should be used with caution, since it does not perform any checks
/// on the validity of the blinding factor!
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
#[cfg(any(feature = "danger", test))]
pub fn deterministic_blind_unchecked(
input: &[u8],
blind: <CS::Group as Group>::Scalar,
) -> Result<PoprfClientBlindResult<CS>> {
Self::deterministic_blind_unchecked_inner(input, blind)
}
/// Can only fail with [`Error::Input`].
fn deterministic_blind_unchecked_inner(
input: &[u8],
blind: <CS::Group as Group>::Scalar,
) -> Result<PoprfClientBlindResult<CS>> {
let blinded_element = deterministic_blind_unchecked::<CS>(input, &blind, Mode::Poprf)?;
Ok(PoprfClientBlindResult {
state: Self {
blind,
blinded_element,
},
message: BlindedElement(blinded_element),
})
}
/// Computes the third step for the multiplicative blinding version of
/// DH-OPRF, in which the client unblinds the server's message.
///
/// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
/// - [`Error::ProofVerification`] if the `proof` failed to verify.
pub fn finalize(
&self,
input: &[u8],
evaluation_element: &EvaluationElement<CS>,
proof: &Proof<CS>,
pk: <CS::Group as Group>::Elem,
info: Option<&[u8]>,
) -> Result<Output<CS::Hash>> {
let clients = core::array::from_ref(self);
let messages = core::array::from_ref(evaluation_element);
let mut batch_result =
Self::batch_finalize(iter::once(input), clients, messages, proof, pk, info)?;
batch_result.next().unwrap()
}
/// Allows for batching of the finalization of multiple [PoprfClient]
/// and [EvaluationElement] pairs
///
/// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
/// - [`Error::Batch`] if the number of `inputs`, `clients` and `messages`
/// don't match or is longer than [`u16::MAX`].
/// - [`Error::ProofVerification`] if the `proof` failed to verify.
///
/// The resulting messages can each fail individually with [`Error::Input`]
/// if the `input` is empty or longer than [`u16::MAX`].
pub fn batch_finalize<'a, II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator, IC, IM>(
inputs: II,
clients: &'a IC,
messages: &'a IM,
proof: &Proof<CS>,
pk: <CS::Group as Group>::Elem,
info: Option<&'a [u8]>,
) -> Result<PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM>>
where
CS: 'a,
&'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
<&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
{
let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?;
finalize_after_unblind::<'a, CS, _, _>(unblinded_elements, inputs, info)
}
/// Only used for test functions
#[cfg(test)]
pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
self.blind
}
}
impl<CS: CipherSuite> PoprfServer<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// 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> {
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
rng.fill_bytes(&mut seed);
Self::new_from_seed(&seed, &[])
}
/// Produces a new instance of a [PoprfServer] using a supplied set of
/// bytes to represent the server's private key
///
/// # Errors
/// [`Error::Deserialization`] if the private key is not a valid point on
/// the group or zero.
pub fn new_with_key(key: &[u8]) -> Result<Self> {
let sk = CS::Group::deserialize_scalar(key)?;
let pk = CS::Group::base_elem() * &sk;
Ok(Self { sk, pk })
}
/// Produces a new instance of a [PoprfServer] using a supplied set of
/// bytes which are used as a seed to derive the server's private key.
///
/// 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.
pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
let (sk, pk) = derive_keypair::<CS>(seed, info, Mode::Poprf)?;
Ok(Self { sk, pk })
}
// Only used for tests
#[cfg(test)]
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
self.sk
}
/// 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.
///
/// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn evaluate<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
blinded_element: &BlindedElement<CS>,
info: Option<&[u8]>,
) -> Result<PoprfServerEvaluateResult<CS>> {
let PoprfServerBatchEvaluatePrepareResult {
mut prepared_evaluation_elements,
prepared_tweak,
} = self.batch_evaluate_prepare(iter::once(blinded_element), info)?;
let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap();
let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element);
let PoprfServerBatchEvaluateFinishResult {
mut messages,
proof,
} = Self::batch_evaluate_finish(
rng,
iter::once(blinded_element),
prepared_evaluation_elements,
&prepared_tweak,
)
.unwrap();
Ok(PoprfServerEvaluateResult {
message: messages.next().unwrap(),
proof,
})
}
/// Allows for batching of the evaluation of multiple [BlindedElement]
/// messages from a [PoprfClient]
///
/// # Errors
/// - [`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_evaluate<'a, R: RngCore + CryptoRng, IE>(
&self,
rng: &mut R,
blinded_elements: &'a IE,
info: Option<&[u8]>,
) -> Result<PoprfServerBatchEvaluateResult<CS>>
where
CS: 'a,
&'a IE: 'a + IntoIterator<Item = &'a BlindedElement<CS>>,
<&'a IE as IntoIterator>::IntoIter: ExactSizeIterator,
{
let PoprfServerBatchEvaluatePrepareResult {
prepared_evaluation_elements,
prepared_tweak,
} = self.batch_evaluate_prepare(blinded_elements.into_iter(), info)?;
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
// This can't fail because we know the size of the inputs.
let PoprfServerBatchEvaluateFinishResult { messages, proof } =
Self::batch_evaluate_finish::<_, _, Vec<_>>(
rng,
blinded_elements.into_iter(),
&prepared_evaluation_elements,
&prepared_tweak,
)
.unwrap();
let messages: Vec<_> = messages.collect();
Ok(PoprfServerBatchEvaluateResult { messages, proof })
}
/// Alternative version of `batch_evaluate` without
/// memory allocation. Returned [`PreparedEvaluationElement`] have to
/// be [`collect`](Iterator::collect)ed and passed into
/// [`batch_evaluate_finish`](Self::batch_evaluate_finish).
///
/// # Errors
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
pub fn batch_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>(
&self,
blinded_elements: I,
info: Option<&[u8]>,
) -> Result<PoprfServerBatchEvaluatePrepareResult<CS, I>>
where
CS: 'a,
{
let tweak = compute_tweak::<CS>(self.sk, info)?;
Ok(PoprfServerBatchEvaluatePrepareResult {
prepared_evaluation_elements: blinded_elements.zip(iter::repeat(tweak)).map(
|(blinded_element, tweak)| {
PreparedEvaluationElement(EvaluationElement(
blinded_element.0 * &CS::Group::invert_scalar(tweak),
))
},
),
prepared_tweak: PoprfPreparedTweak(tweak),
})
}
/// See [`batch_evaluate_prepare`](Self::batch_evaluate_prepare) for more
/// details.
///
/// # Errors
/// [`Error::Batch`] if the number of `blinded_elements` and
/// `prepared_evaluation_elements` don't match or is longer then
/// [`u16::MAX`]
pub fn batch_evaluate_finish<
'a,
'b,
R: RngCore + CryptoRng,
IB: Iterator<Item = &'a BlindedElement<CS>> + ExactSizeIterator,
IE,
>(
rng: &mut R,
blinded_elements: IB,
prepared_evaluation_elements: &'b IE,
prepared_tweak: &PoprfPreparedTweak<CS>,
) -> Result<PoprfServerBatchEvaluateFinishResult<'b, CS, IE>>
where
CS: 'a,
&'b IE: IntoIterator<Item = &'b PreparedEvaluationElement<CS>>,
<&'b IE as IntoIterator>::IntoIter: ExactSizeIterator,
{
let g = CS::Group::base_elem();
let tweak = prepared_tweak.0;
let tweaked_key = g * &tweak;
let proof = generate_proof(
rng,
tweak,
g,
tweaked_key,
prepared_evaluation_elements
.into_iter()
.map(|element| element.0 .0),
blinded_elements.map(|element| element.0),
Mode::Poprf,
)?;
let messages = prepared_evaluation_elements.into_iter().map(<fn(
&PreparedEvaluationElement<CS>,
) -> _>::from(
|element| EvaluationElement(element.0 .0),
));
Ok(PoprfServerBatchEvaluateFinishResult { messages, proof })
}
/// Retrieves the server's public key
pub fn get_public_key(&self) -> <CS::Group as Group>::Elem {
self.pk
}
}
impl<CS: CipherSuite> BlindedElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// Creates a [BlindedElement] from a raw group element.
///
/// # Caution
///
/// This should be used with caution, since it does not perform any checks
/// on the validity of the value itself!
#[cfg(feature = "danger")]
pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
Self(value)
}
/// Exposes the internal value
#[cfg(feature = "danger")]
pub fn value(&self) -> <CS::Group as Group>::Elem {
self.0
}
}
impl<CS: CipherSuite> EvaluationElement<CS>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
/// Creates an [EvaluationElement] from a raw group element.
///
/// # Caution
///
/// This should be used with caution, since it does not perform any checks
/// on the validity of the value itself!
#[cfg(feature = "danger")]
pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
Self(value)
}
/// Exposes the internal value
#[cfg(feature = "danger")]
pub fn value(&self) -> <CS::Group as Group>::Elem {
self.0
}
}
/////////////////////////
// Convenience Structs //
//==================== //
/////////////////////////
/// 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>,
{
/// The state to be persisted on the client
pub state: PoprfClient<CS>,
/// The message to send to the server
pub message: BlindedElement<CS>,
}
/// Concrete return type for [`PoprfClient::batch_finalize`].
pub type PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM> =
FinalizeAfterUnblindResult<'a, CS, PoprfUnblindResult<'a, CS, IC, IM>, II>;
/// 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>,
{
/// The message to send to the client
pub message: EvaluationElement<CS>,
/// The proof for the client to verify
pub proof: Proof<CS>,
}
/// 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>,
{
/// The messages to send to the client
pub messages: Vec<EvaluationElement<CS>>,
/// The proof for the client to verify
pub proof: Proof<CS>,
}
/// Concrete type of [`EvaluationElement`]s in
/// [`PoprfServerBatchEvaluatePrepareResult`].
pub type PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
Zip<I, Repeat<<<CS as CipherSuite>::Group as Group>::Scalar>>,
fn(
(
&BlindedElement<CS>,
<<CS as CipherSuite>::Group as Group>::Scalar,
),
) -> PreparedEvaluationElement<CS>,
>;
/// Prepared tweak by a partially verifiable server batch evaluate prepare.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(crate = "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>,
{
/// Prepared [`EvaluationElement`].
pub prepared_evaluation_elements: PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I>,
/// Prepared tweak.
pub prepared_tweak: PoprfPreparedTweak<CS>,
}
/// Concrete type of [`EvaluationElement`]s in
/// [`PoprfServerBatchEvaluateFinishResult`].
pub type PoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map<
<&'a I as IntoIterator>::IntoIter,
fn(&PreparedEvaluationElement<CS>) -> EvaluationElement<CS>,
>;
/// Contains the fields that are returned by a verifiable server batch evaluate
/// finish.
#[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
pub messages: PoprfServerBatchEvaluateFinishedMessages<'a, CS, I>,
/// The proof for the client to verify
pub proof: Proof<CS>,
}
/////////////////////
// Inner functions //
// =============== //
/////////////////////
/// Inner function for POPRF blind. Computes the tweaked key from the server
/// public key and info.
///
/// Can only fail with [`Error::Info`] or [`Error::Protocol`]
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>,
{
// None for info is treated the same as empty bytes
let info = info.unwrap_or_default();
// framedInfo = "Info" || I2OSP(len(info), 2) || info
// m = G.HashToScalar(framedInfo)
// T = G.ScalarBaseMult(m)
// tweakedKey = T + pkS
// if tweakedKey == G.Identity():
// raise InvalidInputError
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));
// 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 t = CS::Group::base_elem() * &m;
let tweaked_key = t + &pk;
// Check if resulting element
match bool::from(CS::Group::is_identity_elem(tweaked_key)) {
true => Err(Error::Protocol),
false => Ok(tweaked_key),
}
}
/// Inner function for POPRF evaluate. Computes the tweak from the server
/// private key and info.
///
/// Can only fail with [`Error::Info`] and [`Error::Protocol`].
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>,
{
// None for info is treated the same as empty bytes
let info = info.unwrap_or_default();
// framedInfo = "Info" || I2OSP(len(info), 2) || info
// m = G.HashToScalar(framedInfo)
// t = skS + m
// if t == 0:
// raise InverseError
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));
// 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 t = sk + &m;
// Check if resulting element is equal to zero
match bool::from(CS::Group::is_zero_scalar(t)) {
true => Err(Error::Protocol),
false => Ok(t),
}
}
type PoprfUnblindResult<'a, CS, IC, IM> = Map<
Zip<
Map<
<&'a IC as IntoIterator>::IntoIter,
fn(&PoprfClient<CS>) -> <<CS as CipherSuite>::Group as Group>::Scalar,
>,
<&'a IM as IntoIterator>::IntoIter,
>,
fn(
(
<<CS as CipherSuite>::Group as Group>::Scalar,
&'a EvaluationElement<CS>,
),
) -> <<CS as CipherSuite>::Group as Group>::Elem,
>;
/// Can only fail with [`Error::Info`], [`Error::Protocol`], [`Error::Batch] or
/// [`Error::ProofVerification`].
fn poprf_unblind<'a, CS: 'a + CipherSuite, IC, IM>(
clients: &'a IC,
messages: &'a IM,
pk: <CS::Group as Group>::Elem,
proof: &Proof<CS>,
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>>,
<&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
{
let info = info.unwrap_or_default();
let tweaked_key = compute_tweaked_key::<CS>(pk, Some(info))?;
let g = CS::Group::base_elem();
let blinds = clients
.into_iter()
// Convert to `fn` pointer to make a return type possible.
.map(<fn(&PoprfClient<CS>) -> _>::from(|x| x.blind));
let evaluation_elements = messages.into_iter().map(|element| element.0);
let blinded_elements = clients.into_iter().map(|client| client.blinded_element);
verify_proof(
g,
tweaked_key,
evaluation_elements,
blinded_elements,
proof,
Mode::Poprf,
)?;
Ok(blinds
.zip(messages.into_iter())
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
}
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>>,
>;
/// Can only fail with [`Error::Batch`] and returned values can only fail with
/// [`Error::Info`] or [`Error::Input`] individually.
fn finalize_after_unblind<
'a,
CS: CipherSuite,
IE: 'a + Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator,
>(
unblinded_elements: IE,
inputs: II,
info: Option<&'a [u8]>,
) -> Result<FinalizeAfterUnblindResult<'a, CS, IE, II>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
if unblinded_elements.len() != inputs.len() {
return Err(Error::Batch);
}
let info = info.unwrap_or_default();
Ok(unblinded_elements.zip(inputs).zip(iter::repeat(info)).map(
|((unblinded_element, input), info)| {
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(info), 2) || info ||
// I2OSP(len(unblindedElement), 2) || unblindedElement ||
// "Finalize"
// return Hash(hashInput)
let output = CS::Hash::new()
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(input.as_ref())
.chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Info)?)
.chain_update(info.as_ref())
.chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(unblinded_element))
.chain_update(STR_FINALIZE)
.finalize();
Ok(output)
},
))
}
///////////
// Tests //
// ===== //
///////////
#[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::*;
use crate::common::STR_HASH_TO_GROUP;
use crate::Group;
fn prf<CS: CipherSuite>(
input: &[u8],
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 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();
// evaluatedElement = G.ScalarInverse(t) * blindedElement
let res = point * &CS::Group::invert_scalar(t);
finalize_after_unblind::<CS, _, _>(iter::once(res), iter::once(input), Some(info))
.unwrap()
.next()
.unwrap()
.unwrap()
}
fn verifiable_retrieval<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let info = b"info";
let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
let server_result = server
.evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap();
let client_finalize_result = client_blind_result
.state
.finalize(
input,
&server_result.message,
&server_result.proof,
server.get_public_key(),
Some(info),
)
.unwrap();
let res2 = prf::<CS>(input, server.get_private_key(), info, Mode::Poprf);
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>,
{
let input = b"input";
let info = b"info";
let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
let server_result = server
.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));
// Choose a group element that is unlikely to be the right public key
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst).unwrap()
};
let client_finalize_result = client_blind_result.state.finalize(
input,
&server_result.message,
&server_result.proof,
wrong_pk,
Some(info),
);
assert!(client_finalize_result.is_err());
}
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>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
let mut state = client_blind_result.state;
unsafe { ptr::drop_in_place(&mut state) };
assert!(state.serialize().iter().all(|&x| x == 0));
let mut message = client_blind_result.message;
unsafe { ptr::drop_in_place(&mut message) };
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>,
{
let input = b"input";
let info = b"info";
let mut rng = OsRng;
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
let server_result = server
.evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap();
let mut state = server;
unsafe { ptr::drop_in_place(&mut state) };
assert!(state.serialize().iter().all(|&x| x == 0));
let mut message = server_result.message;
unsafe { ptr::drop_in_place(&mut message) };
assert!(message.serialize().iter().all(|&x| x == 0));
let mut proof = server_result.proof;
unsafe { ptr::drop_in_place(&mut proof) };
assert!(proof.serialize().iter().all(|&x| x == 0));
}
#[test]
fn test_functionality() -> Result<()> {
use p256::NistP256;
#[cfg(feature = "ristretto255")]
{
use crate::Ristretto255;
verifiable_retrieval::<Ristretto255>();
verifiable_bad_public_key::<Ristretto255>();
zeroize_verifiable_client::<Ristretto255>();
zeroize_verifiable_server::<Ristretto255>();
}
verifiable_retrieval::<NistP256>();
verifiable_bad_public_key::<NistP256>();
zeroize_verifiable_client::<NistP256>();
zeroize_verifiable_server::<NistP256>();
Ok(())
}
}
+368 -154
View File
@@ -5,224 +5,438 @@
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Handles the serialization of each of the components used
//! in the VOPRF protocol
//! 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 crate::{
ciphersuite::CipherSuite,
errors::InternalError,
group::Group,
voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
},
BlindedElement, CipherSuite, Error, EvaluationElement, Group, OprfClient, OprfServer,
PoprfClient, PoprfServer, Proof, Result, VoprfClient, VoprfServer,
};
use alloc::vec::Vec;
use generic_array::{typenum::Unsigned, GenericArray};
//////////////////////////////////////////////////////////
// Serialization and Deserialization for High-Level API //
// ==================================================== //
//////////////////////////////////////////////////////////
impl<CS: CipherSuite> NonVerifiableClient<CS> {
/// 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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::scalar_as_bytes(self.blind).to_vec(),
self.data.clone(),
]
.concat()
pub fn serialize(&self) -> GenericArray<u8, OprfClientLen<CS>> {
CS::Group::serialize_scalar(self.blind)
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
if input.len() < scalar_len {
return Err(InternalError::SizeError);
}
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
let blind = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
let data = input[scalar_len..].to_vec();
Ok(Self { blind, data })
Ok(Self { blind })
}
}
impl<CS: CipherSuite> VerifiableClient<CS> {
/// Length of [`VoprfClient`] in bytes for serialization.
pub type VoprfClientLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
<<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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::scalar_as_bytes(self.blind).to_vec(),
self.blinded_element.to_arr().to_vec(),
self.data.clone(),
]
.concat()
pub fn serialize(&self) -> GenericArray<u8, VoprfClientLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
VoprfClientLen<CS>: ArrayLength<u8>,
{
<CS::Group as Group>::serialize_scalar(self.blind)
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
if input.len() < scalar_len + elem_len {
return Err(InternalError::SizeError);
}
let blind = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
let blinded_element = CS::Group::from_element_slice(GenericArray::from_slice(
&input[scalar_len..scalar_len + elem_len],
))?;
let data = input[scalar_len + elem_len..].to_vec();
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
Ok(Self {
blind,
blinded_element,
data,
})
}
}
impl<CS: CipherSuite> NonVerifiableServer<CS> {
/// Length of [`PoprfClient`] in bytes for serialization.
pub type PoprfClientLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
<<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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
CS::Group::scalar_as_bytes(self.sk).to_vec()
pub fn serialize(&self) -> GenericArray<u8, PoprfClientLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
PoprfClientLen<CS>: ArrayLength<u8>,
{
<CS::Group as Group>::serialize_scalar(self.blind)
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
if input.len() != scalar_len {
return Err(InternalError::SizeError);
}
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(input))?;
Ok(Self {
blind,
blinded_element,
})
}
}
/// 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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, OprfServerLen<CS>> {
CS::Group::serialize_scalar(self.sk)
}
/// Deserialization from bytes
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
Ok(Self { sk })
}
}
impl<CS: CipherSuite> VerifiableServer<CS> {
/// Length of [`VoprfServer`] in bytes for serialization.
pub type VoprfServerLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
<<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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::scalar_as_bytes(self.sk).to_vec(),
self.pk.to_arr().to_vec(),
]
.concat()
pub fn serialize(&self) -> GenericArray<u8, VoprfServerLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
VoprfServerLen<CS>: ArrayLength<u8>,
{
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
if input.len() != scalar_len + elem_len {
return Err(InternalError::SizeError);
}
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
let pk = CS::Group::from_element_slice(GenericArray::from_slice(&input[scalar_len..]))?;
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
let pk = deserialize_elem::<CS::Group>(&mut input)?;
Ok(Self { sk, pk })
}
}
impl<CS: CipherSuite> Proof<CS> {
/// Length of [`PoprfServer`] in bytes for serialization.
pub type PoprfServerLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
<<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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::scalar_as_bytes(self.c_scalar),
CS::Group::scalar_as_bytes(self.s_scalar),
]
.concat()
pub fn serialize(&self) -> GenericArray<u8, PoprfServerLen<CS>>
where
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
PoprfServerLen<CS>: ArrayLength<u8>,
{
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
if input.len() < scalar_len + scalar_len {
return Err(InternalError::SizeError);
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
let pk = deserialize_elem::<CS::Group>(&mut input)?;
Ok(Self { sk, pk })
}
}
/// Length of [`Proof`] in bytes for serialization.
pub type ProofLen<CS> = Sum<
<<CS as CipherSuite>::Group as Group>::ScalarLen,
<<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>,
{
/// 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>,
{
CS::Group::serialize_scalar(self.c_scalar)
.concat(CS::Group::serialize_scalar(self.s_scalar))
}
/// Deserialization from bytes
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let c_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
let s_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
Ok(Proof { c_scalar, s_scalar })
}
}
/// 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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, BlindedElementLen<CS>> {
CS::Group::serialize_elem(self.0)
}
/// Deserialization from bytes
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let value = deserialize_elem::<CS::Group>(&mut input)?;
Ok(Self(value))
}
}
/// 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>,
{
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, EvaluationElementLen<CS>> {
CS::Group::serialize_elem(self.0)
}
/// Deserialization from bytes
///
/// # Errors
/// [`Error::Deserialization`] if failed to deserialize `input`.
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
let value = deserialize_elem::<CS::Group>(&mut input)?;
Ok(Self(value))
}
}
fn deserialize_elem<G: Group>(input: &mut &[u8]) -> Result<G::Elem> {
let input = input
.take_ext(G::ElemLen::USIZE)
.ok_or(Error::Deserialization)?;
G::deserialize_elem(input)
}
fn deserialize_scalar<G: Group>(input: &mut &[u8]) -> Result<G::Scalar> {
let input = input
.take_ext(G::ScalarLen::USIZE)
.ok_or(Error::Deserialization)?;
G::deserialize_scalar(input)
}
trait SliceExt {
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self>;
}
impl<T> SliceExt for [T] {
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self> {
if take > self.len() {
return None;
}
Ok(Proof {
c_scalar: CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?,
s_scalar: CS::Group::from_scalar_slice(GenericArray::from_slice(&input[scalar_len..]))?,
})
let (front, back) = self.split_at(take);
*self = back;
Some(front)
}
}
impl<CS: CipherSuite> BlindedElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
#[cfg(feature = "serde")]
pub(crate) mod serde {
use core::marker::PhantomData;
use generic_array::GenericArray;
use serde::de::{Deserializer, Error};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use crate::Group;
pub(crate) struct Element<G: Group>(PhantomData<G>);
impl<'de, G: Group> Element<G> {
pub(crate) fn deserialize<D>(deserializer: D) -> Result<G::Elem, D::Error>
where
D: Deserializer<'de>,
{
GenericArray::<_, G::ElemLen>::deserialize(deserializer)
.and_then(|bytes| G::deserialize_elem(&bytes).map_err(D::Error::custom))
}
pub(crate) fn serialize<S>(self_: &G::Elem, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
G::serialize_elem(*self_).serialize(serializer)
}
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
Ok(Self {
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
})
pub(crate) struct Scalar<G: Group>(PhantomData<G>);
impl<'de, G: Group> Scalar<G> {
pub(crate) fn deserialize<D>(deserializer: D) -> Result<G::Scalar, D::Error>
where
D: Deserializer<'de>,
{
GenericArray::<_, G::ScalarLen>::deserialize(deserializer)
.and_then(|bytes| G::deserialize_scalar(&bytes).map_err(D::Error::custom))
}
pub(crate) fn serialize<S>(self_: &G::Scalar, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
G::serialize_scalar(*self_).serialize(serializer)
}
}
}
impl<CS: CipherSuite> EvaluationElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
Ok(Self {
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
})
}
}
//////////////////////
// Helper Functions //
// ================ //
//////////////////////
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Result<alloc::vec::Vec<u8>, InternalError> {
let sizeof_usize = core::mem::size_of::<usize>();
// Check if input >= 256^length
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
return Err(InternalError::SerializationError);
}
if length <= sizeof_usize {
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
}
let mut output = alloc::vec![0u8; length];
output.splice(
length - sizeof_usize..length,
input.to_be_bytes().iter().cloned(),
);
Ok(output)
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, InternalError> {
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
}
#[cfg(test)]
mod unit_tests {
use super::*;
mod test {
use proptest::collection::vec;
use proptest::prelude::*;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp(0, 1).is_ok());
use crate::{
BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof,
VoprfClient, VoprfServer,
};
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).is_err());
macro_rules! test_deserialize {
($item:ident, $bytes:ident) => {
#[cfg(feature = "ristretto255")]
{
let _ = $item::<crate::Ristretto255>::deserialize(&$bytes[..]);
}
assert!(i2osp(256 * 256 - 1, 2).is_ok());
assert!(i2osp(256 * 256, 2).is_err());
assert!(i2osp(256 * 256 + 1, 2).is_err());
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
};
}
proptest! {
#[test]
fn test_nocrash_oprf_client(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(OprfClient, bytes);
}
#[test]
fn test_nocrash_voprf_client(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(VoprfClient, bytes);
}
#[test]
fn test_nocrash_poprf_client(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(PoprfClient, bytes);
}
#[test]
fn test_nocrash_oprf_server(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(OprfServer, bytes);
}
#[test]
fn test_nocrash_voprf_server(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(VoprfServer, bytes);
}
#[test]
fn test_nocrash_poprf_server(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(PoprfServer, bytes);
}
#[test]
fn test_nocrash_blinded_element(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(BlindedElement, bytes);
}
#[test]
fn test_nocrash_evaluation_element(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(EvaluationElement, bytes);
}
#[test]
fn test_nocrash_proof(bytes in vec(any::<u8>(), 0..200)) {
test_deserialize!(Proof, bytes);
}
}
}
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -7,21 +7,20 @@
use alloc::vec::Vec;
use core::cmp::min;
use rand::{CryptoRng, Error, RngCore};
use rand_core::{CryptoRng, Error, RngCore};
/// A simple implementation of `RngCore` for testing purposes.
///
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
///
///
#[derive(Debug, Clone)]
pub struct CycleRng {
v: Vec<u8>,
}
impl CycleRng {
/// Create a `CycleRng`, yielding a sequence starting with
/// `initial` and looping thereafter
/// Create a `CycleRng`, yielding a sequence starting with `initial` and
/// looping thereafter
pub fn new(initial: Vec<u8>) -> Self {
CycleRng { v: initial }
}
+2 -17
View File
@@ -5,22 +5,7 @@
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
mod cfrg_vectors;
mod mock_rng;
mod parser;
mod voprf_test_vectors;
mod voprf_vectors;
/// Ciphersuite definitions for tests
pub(crate) struct Ristretto255Sha512;
impl crate::CipherSuite for Ristretto255Sha512 {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type Hash = sha2::Sha512;
}
#[cfg(feature = "p256")]
pub(crate) struct P256Sha256;
#[cfg(feature = "p256")]
impl crate::CipherSuite for P256Sha256 {
type Group = p256_::ProjectivePoint;
type Hash = sha2::Sha256;
}
mod test_cfrg_vectors;
+8 -6
View File
@@ -5,7 +5,9 @@
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use alloc::string::String;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use alloc::{format, vec};
pub(crate) fn rfc_to_json(input: &str) -> String {
format!("{{\n{}\n}}", parse_ciphersuites(input))
@@ -20,7 +22,7 @@ fn parse_ciphersuites(input: &str) -> String {
for caps in re.captures_iter(input) {
let ciphersuite = format!(
"\"{}\": {{ {} }}",
caps["ciphersuite"].to_string(),
&caps["ciphersuite"],
parse_modes(chunks[count])
);
ciphersuites.push(ciphersuite);
@@ -39,7 +41,7 @@ fn parse_modes(input: &str) -> String {
for caps in re.captures_iter(input) {
let mode = format!(
"\"{}\": [\n {} \n]",
caps["mode"].to_string(),
&caps["mode"],
parse_vectors(chunks[count])
);
modes.push(mode);
@@ -85,7 +87,7 @@ fn parse_params(input: &str) -> String {
// If line contains =, then
if line.contains('=') {
// Clear out any existing string and flush to params
if param.len() > 0 {
if !param.is_empty() {
param += "\"";
params.push(param);
}
@@ -97,11 +99,11 @@ fn parse_params(input: &str) -> String {
param = format!(" \"{}\": \"{}", key, val);
} else {
let s = line.trim().to_string();
if s.contains("~") || s.contains("#") {
if s.contains('~') || s.contains('#') {
// Ignore comment lines
continue;
}
if s.len() > 0 {
if !s.is_empty() {
param += &s;
}
}
+478
View File
@@ -0,0 +1,478 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
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 crate::tests::mock_rng::CycleRng;
use crate::tests::parser::*;
use crate::{
BlindedElement, CipherSuite, EvaluationElement, Group, OprfClient, OprfServer, PoprfClient,
PoprfServer, PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluatePrepareResult,
Proof, Result, VoprfClient, VoprfServer, VoprfServerBatchEvaluateFinishResult,
};
#[derive(Debug)]
struct VOPRFTestVectorParameters {
seed: Vec<u8>,
sksm: Vec<u8>,
pksm: Vec<u8>,
input: Vec<Vec<u8>>,
info: Vec<u8>,
key_info: Vec<u8>,
blind: Vec<Vec<u8>>,
blinded_element: Vec<Vec<u8>>,
evaluation_element: Vec<Vec<u8>>,
proof: Vec<u8>,
proof_random_scalar: Vec<u8>,
output: Vec<Vec<u8>>,
}
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
seed: decode(values, "Seed"),
sksm: decode(values, "skSm"),
pksm: decode(values, "pkSm"),
input: decode_vec(values, "Input"),
info: decode(values, "Info"),
key_info: decode(values, "KeyInfo"),
blind: decode_vec(values, "Blind"),
blinded_element: decode_vec(values, "BlindedElement"),
evaluation_element: decode_vec(values, "EvaluationElement"),
proof: decode(values, "Proof"),
proof_random_scalar: decode(values, "ProofRandomScalar"),
output: decode_vec(values, "Output"),
}
}
fn decode(values: &JsonValue, key: &str) -> Vec<u8> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s).ok())
.unwrap_or_default()
}
fn decode_vec(values: &JsonValue, 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()]),
};
res.unwrap()
}
macro_rules! json_to_test_vectors {
( $v:ident, $cs:expr, $mode:expr ) => {
$v[$cs][$mode]
.members()
.map(|x| populate_test_vectors(&x))
.collect::<Vec<VOPRFTestVectorParameters>>()
};
}
#[test]
fn test_vectors() -> Result<()> {
use p256::NistP256;
let rfc = json::parse(rfc_to_json(super::cfrg_vectors::VECTORS).as_str())
.expect("Could not parse json");
#[cfg(feature = "ristretto255")]
{
use crate::Ristretto255;
let ristretto_oprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("OPRF")
);
assert_ne!(ristretto_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_blind::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_finalize::<Ristretto255>(&ristretto_oprf_tvs)?;
let ristretto_voprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("VOPRF")
);
assert_ne!(ristretto_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_blind::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_finalize::<Ristretto255>(&ristretto_voprf_tvs)?;
let ristretto_poprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("POPRF")
);
assert_ne!(ristretto_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_blind::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_finalize::<Ristretto255>(&ristretto_poprf_tvs)?;
}
let p256_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), 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)?;
test_oprf_evaluate::<NistP256>(&p256_oprf_tvs)?;
test_oprf_finalize::<NistP256>(&p256_oprf_tvs)?;
let p256_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), 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)?;
test_voprf_evaluate::<NistP256>(&p256_voprf_tvs)?;
test_voprf_finalize::<NistP256>(&p256_voprf_tvs)?;
let p256_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), 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)?;
test_poprf_evaluate::<NistP256>(&p256_poprf_tvs)?;
test_poprf_finalize::<NistP256>(&p256_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>,
{
for parameters in tvs {
let server = OprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
assert_eq!(
&parameters.sksm,
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
);
}
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>,
{
for parameters in tvs {
let server = VoprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
assert_eq!(
&parameters.sksm,
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
);
assert_eq!(
&parameters.pksm,
CS::Group::serialize_elem(server.get_public_key()).as_slice()
);
}
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>,
{
for parameters in tvs {
let server = PoprfServer::<CS>::new_from_seed(&parameters.seed, &parameters.key_info)?;
assert_eq!(
&parameters.sksm,
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
);
assert_eq!(
&parameters.pksm,
CS::Group::serialize_elem(server.get_public_key()).as_slice()
);
}
Ok(())
}
// 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>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
let client_result =
OprfClient::<CS>::deterministic_blind_unchecked(&parameters.input[i], blind)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::serialize_scalar(client_result.state.blind).to_vec()
);
assert_eq!(
parameters.blinded_element[i].as_slice(),
client_result.message.serialize().as_slice(),
);
}
}
Ok(())
}
// 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>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
let client_blind_result =
VoprfClient::<CS>::deterministic_blind_unchecked(&parameters.input[i], blind)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec()
);
assert_eq!(
parameters.blinded_element[i].as_slice(),
client_blind_result.message.serialize().as_slice(),
);
}
}
Ok(())
}
// 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>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
let client_blind_result =
PoprfClient::<CS>::deterministic_blind_unchecked(&parameters.input[i], blind)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec()
);
assert_eq!(
parameters.blinded_element[i].as_slice(),
client_blind_result.message.serialize().as_slice(),
);
}
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
let message = server.evaluate(&BlindedElement::deserialize(
&parameters.blinded_element[i],
)?);
assert_eq!(
&parameters.evaluation_element[i],
&message.serialize().as_slice()
);
}
}
Ok(())
}
fn test_voprf_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>,
{
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = VoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let mut blinded_elements = vec![];
for blinded_element_bytes in &parameters.blinded_element {
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
}
let prepared_evaluation_elements = server.batch_evaluate_prepare(blinded_elements.iter());
let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } =
server.batch_evaluate_finish(&mut rng, blinded_elements.iter(), &prepared_elements)?;
let messages: Vec<_> = messages.collect();
for (parameter, message) in parameters.evaluation_element.iter().zip(messages) {
assert_eq!(&parameter, &message.serialize().as_slice());
}
assert_eq!(&parameters.proof, &proof.serialize().as_slice());
}
Ok(())
}
fn test_poprf_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>,
{
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let mut blinded_elements = vec![];
for blinded_element_bytes in &parameters.blinded_element {
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
}
let PoprfServerBatchEvaluatePrepareResult {
prepared_evaluation_elements,
prepared_tweak,
} = server.batch_evaluate_prepare(blinded_elements.iter(), Some(&parameters.info))?;
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
let PoprfServerBatchEvaluateFinishResult { messages, proof } =
PoprfServer::batch_evaluate_finish::<_, _, Vec<_>>(
&mut rng,
blinded_elements.iter(),
&prepared_evaluation_elements,
&prepared_tweak,
)
.unwrap();
let messages: Vec<_> = messages.collect();
for (parameter, message) in parameters.evaluation_element.iter().zip(messages) {
assert_eq!(&parameter, &message.serialize().as_slice());
}
assert_eq!(&parameters.proof, &proof.serialize().as_slice());
}
Ok(())
}
// 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>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let client =
OprfClient::<CS>::from_blind(CS::Group::deserialize_scalar(&parameters.blind[i])?);
let client_finalize_result = client.finalize(
&parameters.input[i],
&EvaluationElement::deserialize(&parameters.evaluation_element[i])?,
)?;
assert_eq!(&parameters.output[i], &client_finalize_result.to_vec());
}
}
Ok(())
}
fn test_voprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
let client = VoprfClient::<CS>::from_blind_and_element(
CS::Group::deserialize_scalar(&parameters.blind[i])?,
CS::Group::deserialize_elem(&parameters.blinded_element[i])?,
);
clients.push(client.clone());
}
let messages: Vec<_> = parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect();
let batch_result = VoprfClient::batch_finalize(
&parameters.input,
&clients,
&messages,
&Proof::deserialize(&parameters.proof)?,
CS::Group::deserialize_elem(&parameters.pksm)?,
)?;
assert_eq!(
parameters.output,
batch_result
.map(|arr| arr.map(|message| message.to_vec()))
.collect::<Result<Vec<_>>>()?
);
}
Ok(())
}
fn test_poprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
let blind = CS::Group::deserialize_scalar(&parameters.blind[i])?;
let client_blind_result =
PoprfClient::<CS>::deterministic_blind_unchecked(&parameters.input[i], blind)?;
let client = client_blind_result.state;
clients.push(client.clone());
}
let messages: Vec<_> = parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect();
let batch_result = PoprfClient::batch_finalize(
parameters.input.iter().map(|input| input.as_slice()),
&clients,
&messages,
&Proof::deserialize(&parameters.proof)?,
CS::Group::deserialize_elem(&parameters.pksm)?,
Some(&parameters.info),
)?;
let result: Vec<Vec<u8>> = batch_result.map(|arr| arr.unwrap().to_vec()).collect();
assert_eq!(parameters.output, result);
}
Ok(())
}
-335
View File
@@ -1,335 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::InternalError,
group::Group,
tests::{mock_rng::CycleRng, parser::*},
voprf::{
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
NonVerifiableServer, Proof, VerifiableClient, VerifiableServer,
},
};
use alloc::string::ToString;
use alloc::vec::Vec;
use generic_array::GenericArray;
use json::JsonValue;
#[derive(Debug)]
struct VOPRFTestVectorParameters {
seed: Vec<u8>,
sksm: Vec<u8>,
pksm: Vec<u8>,
input: Vec<Vec<u8>>,
info: Vec<u8>,
blind: Vec<Vec<u8>>,
blinded_element: Vec<Vec<u8>>,
evaluation_element: Vec<Vec<u8>>,
proof: Vec<u8>,
proof_random_scalar: Vec<u8>,
output: Vec<Vec<u8>>,
}
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
seed: decode(values, "seed"),
sksm: decode(values, "skSm"),
pksm: decode(values, "pkSm"),
input: decode_vec(&values, "Input"),
info: decode(values, "Info"),
blind: decode_vec(&values, "Blind"),
blinded_element: decode_vec(&values, "BlindedElement"),
evaluation_element: decode_vec(&values, "EvaluationElement"),
proof: decode(values, "Proof"),
proof_random_scalar: decode(values, "ProofRandomScalar"),
output: decode_vec(&values, "Output"),
}
}
fn decode(values: &JsonValue, key: &str) -> Vec<u8> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
.unwrap_or(vec![])
}
fn decode_vec(values: &JsonValue, 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.to_string()).unwrap())
.collect(),
),
false => Some(vec![hex::decode(&s.to_string()).unwrap()]),
};
res.unwrap()
}
macro_rules! json_to_test_vectors {
( $v:ident, $cs:expr, $mode:expr ) => {
$v[$cs][$mode]
.members()
.map(|x| populate_test_vectors(&x))
.collect::<Vec<VOPRFTestVectorParameters>>()
};
}
#[test]
fn test_vectors() -> Result<(), InternalError> {
let rfc = json::parse(rfc_to_json(super::voprf_vectors::VECTORS).as_str())
.expect("Could not parse json");
use crate::tests::Ristretto255Sha512;
let ristretto_base_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("Base")
);
let ristretto_verifiable_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255, SHA-512"),
String::from("Verifiable")
);
test_base_seed_to_key::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_blind::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_evaluate::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_finalize::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_verifiable_seed_to_key::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_blind::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_evaluate::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_finalize::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
#[cfg(feature = "p256")]
{
use crate::tests::P256Sha256;
let p256_base_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("Base"));
let p256_verifiable_tvs = json_to_test_vectors!(
rfc,
String::from("P-256, SHA-256"),
String::from("Verifiable")
);
test_base_seed_to_key::<P256Sha256>(&p256_base_tvs)?;
test_base_blind::<P256Sha256>(&p256_base_tvs)?;
test_base_evaluate::<P256Sha256>(&p256_base_tvs)?;
test_base_finalize::<P256Sha256>(&p256_base_tvs)?;
test_verifiable_seed_to_key::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_blind::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_evaluate::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_finalize::<P256Sha256>(&p256_verifiable_tvs)?;
}
Ok(())
}
fn test_base_seed_to_key<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = NonVerifiableServer::<CS>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
);
}
Ok(())
}
fn test_verifiable_seed_to_key<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = VerifiableServer::<CS>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
);
assert_eq!(&parameters.pksm, &server.get_public_key().to_arr().to_vec());
}
Ok(())
}
// Tests input -> blind, blinded_element
fn test_base_blind<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let client_result = NonVerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
&client_result.message.serialize()
);
}
}
Ok(())
}
// Tests input -> blind, blinded_element
fn test_verifiable_blind<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let client_blind_result =
VerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client_blind_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
&client_blind_result.message.serialize()
);
}
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
fn test_base_evaluate<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = NonVerifiableServer::<CS>::new_with_key(&parameters.sksm)?;
let server_result = server.evaluate(
BlindedElement::deserialize(&parameters.blinded_element[i])?,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(
&parameters.evaluation_element[i],
&server_result.message.serialize()
);
}
}
Ok(())
}
fn test_verifiable_evaluate<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = VerifiableServer::<CS>::new_with_key(&parameters.sksm)?;
let mut blinded_elements = vec![];
for blinded_element_bytes in &parameters.blinded_element {
blinded_elements.push(BlindedElement::deserialize(&blinded_element_bytes)?);
}
let batch_evaluate_result = server.batch_evaluate(
&mut rng,
&blinded_elements,
&Metadata(parameters.info.clone()),
)?;
for i in 0..parameters.evaluation_element.len() {
assert_eq!(
&parameters.evaluation_element[i],
&batch_evaluate_result.messages[i].serialize(),
);
}
assert_eq!(&parameters.proof, &batch_evaluate_result.proof.serialize());
}
Ok(())
}
// Tests input, blind, evaluation_element -> output
fn test_base_finalize<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let client = NonVerifiableClient::<CS>::from_data_and_blind(
&parameters.input[i],
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
);
let client_finalize_result = client.finalize(
EvaluationElement::deserialize(&parameters.evaluation_element[i])?,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(
&parameters.output[i],
&client_finalize_result.output.to_vec()
);
}
}
Ok(())
}
fn test_verifiable_finalize<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
let client = VerifiableClient::<CS>::from_data_and_blind(
&parameters.input[i],
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
&<CS::Group as Group>::from_element_slice(&GenericArray::clone_from_slice(
&parameters.blinded_element[i],
))?,
);
clients.push(client.clone());
}
let batch_finalize_input = BatchFinalizeInput::new(
clients,
parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect(),
);
let batch_result = VerifiableClient::batch_finalize(
batch_finalize_input,
Proof::deserialize(&parameters.proof)?,
CS::Group::from_element_slice(GenericArray::from_slice(&parameters.pksm))?,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(
parameters.output,
batch_result
.outputs
.iter()
.map(|arr| arr.to_vec())
.collect::<Vec<Vec<u8>>>()
);
}
Ok(())
}
-642
View File
@@ -1,642 +0,0 @@
// Copyright (c) Facebook, Inc. and its 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
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! The VOPRF test vectors taken from:
//! https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
pub(crate) static VECTORS: &str = r#"
## OPRF(ristretto255, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = caeff69352df4905a9121a4997704ca8cee1524a110819eb87deba1a39ec1
701
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf
8e03
BlindedElement = fc20e03aff3a9de9b37e8d35886ade11ec7d85c2a1fb5bb0b16
86c64e07ac467
EvaluationElement = 922e4c04b9f3b3e795d322a306c0ab9d96b667df9b949c05
2c8c75435a9dbf2f
Output = 9e857d0e8523b8eb9e995d455ae6ae19f75d85ac8b5df62c50616fb5aa0
ced3da5646698089c36dead28f9ad8e489fc0ee1c8e168725c38ed50f3783a5c520c
e
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037
e50b
BlindedElement = 483d4f39de5ff77fa0f9a0ad2334dd5bf87f2cda868539d21de
67ce49e7d1536
EvaluationElement = 6eef6ee53c6fb17c77ae47e78bdca2e1094f98785e7b9a14
f09be20797dad656
Output = b090b2ff80028771c14fecf2f37c1b14e46deec59c83d3b943c51d315bd
3bf7d32c399ed0c4ce6003339ab9ed4ad168bfb595e43530c9d73ff02ab0f1263d93
b
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = ac37d5850510299406ea8eb8fa226a7bfc2467a4b070d6c7bf667948b9600
b00
pkSm = 0c0254e22063cae3e1bae02fb6fa20882664a117c0278eda6bda3372c0dd9
860
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e326
3503
BlindedElement = 3a0a53f2c57e5ee0d89e394087f8e5f95b24159db01c31933a0
7f0e6414c954d
EvaluationElement = f8a50ed35a477b0cde91d926e1bc5ae59b97d5bd0dda51a7
28b0f036ec557d79
Proof = 7a5375eb1dbad259431f5c294e816a1c1483c279748da1a75d91f8a81438
ea08355d4087d4d848b46878dcc8fb5849ac7a09133382c2c6129564a7f7b4b7bf01
ProofRandomScalar = 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9d
bcec831b8c681a09
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
a
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd4171
ea02
BlindedElement = a86dd4544d0f3ea973926054230767dff16016215f2d73f26d3
f86a81f38cf1a
EvaluationElement = 9e47810f1de1b57ebe163a95c170ec165a2063f872155c37
6d94e8de2157af70
Proof = 61075125d851d5164b0aa1a4d5ddeebaf097266450ac6019579af5f7abd1
90088eb0f6f1e7f9d8bfddbc21ae3c25a065e6c4e797d15f345ed4fb9ee468d24c0a
ProofRandomScalar = 74ae06fd50d5f26c2519bd7b184f45dd3ef2cb50197d42df
9d013f7d6c312a0b
Output = fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f2
18047d1fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0
b
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 80513e77795feeec6d2c450589b0e1b178febd5c193a9fcba0d27f0a06e0
d50f,533c2e6d91c934f919ac218973be55ba0d7b234160a0d4cf3bddafbda99e2e0
c
BlindedElement = c24645d6378a4a86ec4682a8d86f368b1e7db870fd709a45102
492bcdc17e904,0e5ec78f839a8b6e86999bc180602690a4daae57bf5d7f827f3d40
2f56cc6c51
EvaluationElement = 3afe48eab00493eb1b073e95f57a456cde9aefe463dd1e6d
0144bf6e99ce411c,daaf9421318fd2c7fcdf369cb348748cf4dd177cce30ee4d13c
eb1644b85b653
Proof = 601381ecbe127ada04c057b8b1fc21d912f71e49252780dd0d0ac768b233
ce035f9b489a994c1d14b92d603ebcffee4f5cfadc953f69bb62648c6e662613ae00
ProofRandomScalar = 3af5aec325791592eee4a8860522f8444c8e71ac33af5186
a9706137886dce08
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
a,fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f218047d1
fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0b
~~~
## OPRF(decaf448, SHAKE-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 78f2622804104209f7e015370ff98f4a3cbf311e6784e9f4944f8a252dc08
e916d9ab1a60dc905f0e56631903ecd4ae6e15291776d61460b
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = d1080372f0fcf8c5eace50914e7127f576725f215cc7c111673c635ce668
bbbb9b50601ad89b358ab8c23ed0b6c9d040365ec9d060868714
BlindedElement = 984e0a443ee194090737df4afb402253f216b77650c91d252b6
638e1179723d51a4154b88eae396f1320f5df3c4b17f779516c456e364bd1
EvaluationElement = de477252a5ff3c7d51ce159cb8ccf1865d8c7d3402824163
8d80971f13a59d87b2b1036341b98089555ab088278391794c49bbb052fdbcff
Output = df8f910c3b84d1f3ca6afd1992768608a20f2ad7b770e9d89d303c88ba1
5bb7d991f2f7ffd5b5b51fa3bcf8fa06779609497f6c0ae4e9cb2dcd48c68b4ac6b9
4
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = aed1ffa44fd8f0ed16373606a3cf7df589cca86d8ea1abbf5768771dbef3
d401c74ae55ba1e28b9565e1e4018eb261a14134a4ce60c1c718
BlindedElement = 4aa751f84b2634b73efa364b03e60b92b84f457576e6b369eea
b76140e3859d10d2e98174f13f5a2c70670529ccf093d5f1aaf355b4f830b
EvaluationElement = 085ea1cb452a2fb15b3a0d0e1c86899c7ea49fe2e4856ef4
f95bc2542eec610fc09b0fe7d7ed7389d86af6a646695b7ad46527dc2a936aa4
Output = b57516a737879ece1110ad5d051ac0a6c54e1dcd989c907721ecebab5b4
5877cc693c3c05d0bd416c5a9ceba36de41a0a31679c146fe4c110c64b056eba1720
b
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 9eb722f7fee9f61f24ad31bc42309f73648cf4393929e8f5f333fe10c6975
c827a1eba4e03ae2fa8735db2f63f6c98c7af6010e64c81f535
pkSm = b6e2751176d57836fe1dfbdbbdc78a1b5c5a52f831226c9d8dfdf5daf8f46
6e310e80978e9b81c387f5bc85cc7ef5567f4dd3ba7674579a2
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 4c936db1779a621b6c71475ac3111fd5703a59b713929f36dfd1e892a7fe
814479c93d8b4b6e11d1f6fe5351e51457b665fa7b76074e531f
BlindedElement = d0b8e2eecad2816d45c1f8a072fe6db77d18f4b26f0889c98e2
ef856ac5df82090c1fbeac9c8e732f192b66c3b4c3f1e446ab8910c86be2f
EvaluationElement = d29849d8ad1e651328e8119003debd9ecd54cc786a5eb8ae
ea56487ffc09120e98792f9475605488d16623b8e3cfa5af1ec27e76bc841b75
Proof = 8b3b8f0c9eb22527e419f5a03d4d3f34cf725837424a38c5b4f88c7759f7
a54bade57b7930bfeff051be9bfeaabc8976ed407398e0ce462a062e068a8d57bc1c
411bc42fe714626cfb92ad854a56636c2b83f2b5215c2ff531b22e4d37031523db20
3556959e275b46b84303ed23fc37
ProofRandomScalar = 1b3f5a55b2f18f8c53d4ecf2e1c27e1028f1c345bb504486
4aa9dd8439d7520a7ba6183d50ef08bdf6c781aa465660c93e8195a8d231b62f
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
d
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 15b3355179392f40c3d5a15f0d5ffc354e340454ec779f575e4573a3886a
b5e57e4da2985cea9e32f6d95539ce2c7189e1bd7462e8c5483a
BlindedElement = 0e7ddd85c8bc5382e908241c6151afe23a41e0396759b5e38a9
affd996cd822bca242a499793555fc15f07bffdaaa93b42568b307fbdca0f
EvaluationElement = 4c81e29e8a9502fa02e00cb09cf40d9b98988ac9b4bce7cc
a0656caeb0926b59c7000d7fe6c5dd814f831864547d2360d223a50077bd04fe
Proof = 74fc8fbf2e669dc5d25898ea8ce45d1d3eb97edb4b7c3cee39865a3c66da
6b7bad4ad3e77794d6f5e82fa8a645b9b973a8612bfcd1194302f700ee3433e876d8
3f96bb70f19ff292605ad4c9466fd71dbc2ed22ade0130574e5ee343ef45d42e834a
11a19fd6f5b1b5ef910bcccf731b
ProofRandomScalar = 2f2e9955be83a4b25743ebd3618d4fad8b7288477da50bed
9befa58af639ddd950fec34205f8a4f166fadcb8fa71a3ffdd2e98f4c8ef5e26
Output = 2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c
07f59055e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f
9
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 614bb578f29cc677ea9e7aea3e4839413997e020f9377b63c13584156a09
a46dd2a425c41eac0e313a47e99d05df72c6e1d58e6592577a0d,4c115060bca87db
7d73e00cbb8559f84cb7a221b235b0950a0ab553f03f10e1386abe954011b7da62bb
6599418ef90b5d4ea98cc28aff517
BlindedElement = 5e481a4d7eaa5bab831f53f9a6311851dafd4318c6462eed4f6
15004afdb082da2f99670b0963985faac21c30eea19aacfc441412edb4c0b,8e043b
9b7afeafa07e39d9b8b88957ff07d69124b1a2b841e18c9ffb52ebf0c25144eb2501
a1d7983a44604f33a36e925eebc9bec65d9c54
EvaluationElement = 8a0d34fdb0b55121421546ff952c7bd3cbe469926ff9ad4f
aeba243823955529eeae4f1a7a64cd055ec01baa041a99dfbe1a67ca4d59f93d,5e8
6e0b41cc88186ee0003baa46535e71acd98453b298976b92be2cca2646e88620f55d
f6bf4754456dfd8d84f6889c17b5ff93052325a1a
Proof = 1ff624a102b99771c76a9414e9b3f33127897d971bc84a922e464805e4a9
f27b889922030adebbbd58e0ab618ade9c84bfe8aa226176f11f432958ea1e6f6926
3aef51db9efb23ee504d233c17e9077c0373401da167637a1df4eafd9c2537c9f89c
103f9e635931fe2042419dd9bd37
ProofRandomScalar = a614f1894bcf6a1c7cef33909b794fe6e69a642b20f4c911
8febffaf6b6a31471fe7794aa77ced123f07e56cc27de60b0ab106c0b8eab127
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
d,2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c07f5905
5e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f9
~~~
## OPRF(P-256, SHA-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = a1b2355828f2c76de6749af9d093bd9fe0f2cada3ec653cd9a6d3126a7a78
27b
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 5d9e7f6efd3093c32ecceabd57fb03cf760c926d2a7bfa265babf29ec98a
f0d0
BlindedElement = 03e3c379698da853d9844098fa0ac676970d5ec24167b598714
cd2ee188604ddd2
EvaluationElement = 030d8d882120e8fa67ef978a9abac506acd5ec731b8e8d6f
15035e29241dd2ced2
Output = ab653a4f3b357177b125e1c6d0bd2c0bc409b7ed5f48c99537fbd7fd11e
f8133
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 825155ab61f17605af2ae2e935c78d857c9407bcd45128d57d338f1671b5
fcbe
BlindedElement = 030b40be181ffbb3c3ae4a4911287c43261f5e4034781def69c
51608f372a02102
EvaluationElement = 03991df04e3e526d457065b6eafc855aa2fc4528c22d2b51
6a3c71227b1b488f44
Output = eca4df985f7c49b091c3ce4217be1f26cdc6a148b681ed1f1638d09dfd2
13e6e
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 4e7804245a743c59d624457677294e04a8bc4bdcd94f0d3bd54f568067489
d34
pkSm = 03b51a0af95c819b09ee80c2056cf0ab0551a5355266d3a0aaff90c3fe915
ed892
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = cee64d86fd20ab4caa264a26c0e3d42fb773b3173ba76f9588c9b14779bd
8d91
BlindedElement = 0222f5dba2da1ec7bd1086d0e04894ef1da1c11163daf376b2b
c76cc51edb16815
EvaluationElement = 02f2767135f75f69b257675b38f2bcd50338a655a5092166
3c8942ca61ea7d3c29
Proof = ffa082fc9f9a287e7edc50e3ad879ee13aebd24b69124792bdf047c643f7
0af2b50907b2fa188b90aff3b25e1d9abb02e9e2c8bfdc525c61ca008428940fca64
ProofRandomScalar = 70a5204b2b606f5a28328916e1e5ea5a17862d7a261fdd6d
959759758d5e34ac
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
94448
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5c4b401063eff0bf242b4cd534a79bacfc2e715b2db1e7a3ad4ff8af1b24
daa2
BlindedElement = 02f84403d1ceb40a3668349f7c349f806d2c858785853324c66
7505018d13ee160
EvaluationElement = 0216d7d342ef50113244b444dfedaec78810959e40fef0a6
922658d44accb1e9c1
Proof = f496e58818c25ffb386f22ceb57a83da1200612b67aaa07608b3375c25b2
97e03e67d1f6094a8012725dc63a0c2f4f870173b97a3daa03588f777655a087fbbf
ProofRandomScalar = 3b9217801b5d51cef66d9fdbd94a53533e7c5057e09e2200
65ea8c257c0dd606
Output = 90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b9
2fe28
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = f0c7822ba317fb5e86028c44b92bd3aedcf6744d388ca013ef33edd36930
4eda,3b9631be9f8b274d9aaf671bfb6a775229bf435021b89c683259773bc686956
b
BlindedElement = 02a840214a74345570dcadfc927e726901b257b447234fac509
0a1830295ca736c,039a5a8152abb0154b4d79a90486e358ea325980f0bf590524c4
460f700454238f
EvaluationElement = 025991aac0b0c79bb1185c0b1e64964656634dfcd755cdf5
da9ee52be0b5d5f742,03319e3baba8fa7f60dab49ef0ba68b7a85bccb5d4968643e
2f029b6c0826911d1
Proof = 51b5ed453168480a2e95863cda1f4d28ad5bc91e8c9c75d788569aea1679
794a642087db120a2b3ce839f57041801f37cd4a6c05b69b327b877810293f7b09a8
ProofRandomScalar = 8306b863276ae74049615162a416d507a6532c99c1ea3f03
d05f6e78dc1edabe
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
94448,90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b92fe
28
~~~
## OPRF(P-384, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = ef1b52c12cdf43dc260bf5425a30cde7d708ec34b38dcfbdc2946d7baf525
361e797f6a98f1ebd80f64865f21cde1c6d
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 359073c015b92d15450f7fb395bf52c6ea98384c491fe4e4d423b59de7b0
df382902c13bdc9993d3717bda68fc080b99
BlindedElement = 02fa3115c21ffcacc09ca470729b725781f84333e217cfeec2b
8ba6a54ce492ede7ead3714c5b177427ef853effb1b5c24
EvaluationElement = 033a4bdea2693686e4ce467c8a5cdfc41b86ad20aaaa9bc1
6e75b59dbd41dab0bc9af0041e551ece3b4c9fb2315d8d1fa9
Output = a5a0ef3fb964a36097662d1258ef0f93b224ddd81a356c37d5dd05a885a
0b6722b90c1f5181637fece7ed180ba053da23bf35cef7a87dcba75562cb7a264001
8
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 21ece4f9b6ffd01ce82082545413bd9bb5e8f3c63b86ae88d9ce0530b01c
b1c23382c7ec9bdd6e75898e4877d8e2bc17
BlindedElement = 025fddc89a832089a59120df742acb34dba82b26afcae977961
57df238b5905c494a23c56b1f485cbbff78d31df7fa1492
EvaluationElement = 02f8b59813663e7965c219c113c560482cbea7ca4c412a0c
f3fd855ee7d543ae926d29ace85296f195f988be284b2347f6
Output = f2a0b355cae4ae2c717d0b48e39c0ee356db3ca446fddf85cddb74f397e
b85046da62d0d85d55d19d39dd9b68fcc39379ec6d3b93ba33909fcc96361d225cdd
e
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 67ee1c9e67566d87bfcca9e5dac4bfdb8bdd727c031133fac2aa9ba6c41e6
1e5f8fd401b5d76c7d54b15b15932797479
pkSm = 029b51b2ce9c499f2056e65e0f41d60960f9c4795c0cf94af273ce840c20b
e4cdf87690b6b121b37d399b49afcc2ec9ac3
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 102f6338df84c9602bfa9e7d690b1f7a173d07e6d54a419db4a6308f8b09
589e4283efb9cd1ee4061c6bf884e60a8774
BlindedElement = 02a1f41323e91a6ac9fbbb5b8e4c7c58a4c5bcbaa4195557182
cd59e826dc847f1e077de1d402ac92eafe322461fc0d582
EvaluationElement = 03af3164f8721a57931f92884b43c58ff0ed1be249f7e1c9
3033a5909f0ffc59ed3fea9452ec5c9cfb865b8bd2e65cd209
Proof = 44108ca9b342f4d7e31a250aa9f41afb0de840e113dbb6bb82b5e6735aef
18a20867a63628be6e109d2d687e1faa8888270f1173bc6f916e21142096d23d1719
4edf844074922c287a50182f87bbb5fc3a966c8851dd6799ec5cfe59c7063c7f
ProofRandomScalar = 90f67cafc0ffaa7a1e1d1ced3c477fea691e696032c8709c
86cbcda2b184ad0029d29abeabede9788d11782429bff297
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
0
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 8aec1d0c3d16afd032da7ba961449a56cec6fb918e932b06d5778ac7f67b
ecfb3e3869237f74106241777f230582e84a
BlindedElement = 02b3465d70f76de3eaf6ecb8080490288f741c622c06d023bd1
80a55a2e3e4eaad08533651f9d278a3f59cec8277780303
EvaluationElement = 03a53e01901893585437cd48a1eea1188fc8e9275a80cf43
370a451c476dae3b84ca8c7bf44fcac2fa3eeab933b25da0c3
Proof = 5ebc467e78ae29f7d741221df0ee67285df72ec482fdc8e5bde7e588b12f
cba86f4f116c23ee6b32c0f38f2daac67e869e53e7e0494cc883e4984daf10a55819
bbb5ce7e9005f143b3dda88d8a35649269a4658a98c81c814097d15a3dcf4dbe
ProofRandomScalar = bb1876a7f7165ac7ec79bfd5213ea2e374252f29a6e19915
f81b0c7dcea93ce6580e089ede31c1b6b5b33494581b4868
Output = 5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920
267a40b246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92
c
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 41fabd4722d92472d858051ce9ad1a533176a862c697b2c392aff2aeb77e
b20c2ae6ba52fe31e13e03bf1d9f39878b23,51171628f1d28bb7402ca4aea6465e2
67b7f977a1fb71593281099ef2625644aee0b6c5f5e6e01a2b052b3bd4caf539b
BlindedElement = 02d715dfce1a0724071fa8e530d79f7b234a31739a64166e0fe
21fa6fa0fe19e1ab5e468becca899f31e365c47f3efb2ef,028dfd0c7a38b4cb8477
cae34f041344fb44fc9e55bfa3cf55ab7b4764b74accc7b49c0ff09a524598033dad
1152fb3a1c
EvaluationElement = 03f9a8c81c108201888eb86348c6f80691d99425272972b5
bf41d3038af0eeb04d60edd9ea288625a7166a8c17cea0083f,02abb31980533dbf7
eb5fee0a8969089b3e16585a2cd41a34067592a2021b1b4ea3d1cef3e7c87a6f284c
0e45546c92d98
Proof = f0f7bd2723c3460d5c5ab03092c6861fb34253470ef430dac9aeac6ce489
84b28d91178061cba02e3e911c4aa97229d519755db385ddd08064fdf8405897d1de
a472688934088505e89dcff91081fec1d2e37c1d4c5a9dddbdd358aa89f63b46
ProofRandomScalar = 1b538ff23749be19e92df82df1acd3f606cc9faa9dc7ab25
1997738a3a232f352c2059c25684e6ccea420f8d0c793fa0
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
0,5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920267a40b
246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92c
~~~
## OPRF(P-521, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 016ee706f30ce7e15e4ffa3114c7d59a7b6f302d531ca60419be39d1cd43e
e13b1fc8398b7f63a900cdc49c6e99f65a74403db2fa739927a2ee288cff857d9d84
ecf
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 01b983705fcc9a39607288b935b0797ac6b3c4b2e848823ac9ae16b3a3b5
816be03432370deb7c3c17d9fc7cb4e0ce646e04e42d638e0fa7a434ed340772a8b5
d626
BlindedElement = 0301f0a8c68e58f5571bd39fe3b0b2aa055a8c34e3d68ba0d2e
d177db0bc7575d477ed8f557596feb5ac568fe738eee8cff7dcb56dc78f52bf381c0
912e0e84b5a3f5b
EvaluationElement = 0200d7b1131aa9f8c365de7bd7903738f61bdecfaada375a
ba3905bdaad1301c7cd537f69abff04140ccca29a4c46cb4a036160e55a9621210b3
71d84646b0199571fa
Output = 61eea8fedfa9338dd22fac279f1f3f9e96693919c59ea3918c7a441115e
6bdecb1d05b5da55d4024858c92d3911a81d4eca362123b2911e5dc58591bf7be29c
7
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01a03b1096b0316bc8567c89bd70267d35c8ddcb2be2cdc867089a2eb5cf
471b1e6eb4b043b9644c8539857abe3a2022e9c9fd6a1695bbabe8add48bcd149ff3
b841
BlindedElement = 030099c35342a43221c6e03debfb17bad71b62e04c9242aa6e9
f2f915163ef4f5b8b7fe1740a4d636c36bd5c73ca39c69992dc7f6dff8f232125efc
22af4df8352fea2
EvaluationElement = 0300ceeba6751486eecc479ab2259e3a57c13b0710f61c82
87acad60624974b76ea242dbcae3a9daad1bdc9c49012c8d8b384d510980cc1ef8fa
8d10502748ce63d93f
Output = 6682273a5199b2454a706cac557008e2264580ac39b6995e1f47130b985
d1015de7713d3bdb121212a68de2ece73bf72e41738a01c23428753c44e3dd39b5de
3
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 0017674057e06c5e3e8a331f2dc3558540701c9cd0f4c19126d5972af6a01
447b312d05a06dab3e9e07c891d749444c27ede0897ad42aea03b887eb5db93e3f29
a86
pkSm = 0201ee4e2eaa74728f577f4bb282c5440cd454fdee1d79b15a36d34b5e5a1
25e3ccc0f99e32cc0a6a15b5652a0c8a424860c6753f685d0e1e150ceba24ca3386f
29216
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 00bbb82117c88bbd91b8954e16c0b9ceed3ce992b198be1ebfba9ba970db
d75beefbfc6d056b7f7ba1ef79f4facbf2d912c26ce2ecc5bb8d66419b379952e96b
d6f5
BlindedElement = 0200357f949a0a0bdfeb682734dbdeb778f3845045617b21436
27753332e2e75458ab183b12635c75e19afaf56981e7755803026842db1b22fa42c8
61413d07ff86545
EvaluationElement = 0201d636bac3f77c1091b337daae32259a3eacd57e3c0fb1
444fe5ce22af6acdcef4a46a2b5e169aa8d0e26ec2a3621c15dd366ba1978dae761c
1ef3dac63c60cbee88
Proof = 011ebe27ebc79e5679b643c6b3a51333499c7abee86c092181c0a8e7e539
e0ba30b1c128666708c753696ace2aa789c4975b0b80d6241a1dafe85c39a7338d1e
20d00131c8a81b5f64209f8fe53e8c6a00789a893f20596198e2521275e05d925298
08e9f54030fc8be2ce78c6df0d29e6fd7d8e623e0ccc7b19b194493dacd2a4eb3a32
ProofRandomScalar = 00ce4f0d824939827888f4c28773466f3c0a05741260040b
c9f302a4fea13f1d8f2f6b92a02a32d5eb06f81de7960470f06169bee12cf47965b7
2a59946ca3879670
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
5
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 009055c99bf9591cb0eab2a72d044c05ca2cc2ef9b609a38546f74b6d688
f70cf205f782fa11a0d61b2f5a8a2a1143368327f3077c68a1545e9aafbba6a90dc0
d40a
BlindedElement = 030185e431f056e75ba7fac49da70790031daa333d16f05e1de
471e24afe0ed985c770ce77bd1bebec527e9a76feecc6afd92c5fd00481ba7fb843d
2aab52337cb716e
EvaluationElement = 02000859e1abc2ed28086b854ec5ae72311244fdeedf81d7
69af6a6f2c83f00fa48df1f1a0c0b6fac84cc654b7757ac042107a6b3043e483bb3b
74de5d6c301b20e8f6
Proof = 01629dd5af14c7414801d879b1018ce06bcc5c5d0a64ca422b76aaa531c8
ecca630919fb4b51fa60fdc215f73e67e8d617d55ca6a227343d434d5e0f487567f8
5bfa016959443267bb7d9a5c5e5b1c4d20026394b4edaca7dfbc1aa3b3c2020cf995
79cf276c0e84f0cb5a820226fa3b81d42de2db39d8412642e70428e485a61ee9d760
ProofRandomScalar = 00b5dfc19eb96faba6382ec845097904db87240b9dd47b1e
487ec625f11a7ba2cc3de74c5078a81806f74dd65065273c5bd886c7f87ff8c5f39f
90320718eff747e3
Output = 7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d
1b2e936669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c13011057
1
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01c6cf092d80c7cf2cb55388d899515238094c800bdd9c65f71780ba85f5
ae9b4703e17e559ca3ccd1944f9a70536c175f11a827452672b60d4e9f89eba28104
6e29,00cba1ba1a337759061965a423d9d3d6e1e1006dc8984ad28a4c93ecfc36fc2
171046b3c4284855cfa2434ed98db9e68a597db2c14728fade716a6a82d600444b26
e
BlindedElement = 0301978860af75cd69acbc93e8c9fc530e5d2b2208da42c65bf
e079f0f6e0b3fc6080556c10739271d2a8fe578409d4fa9b19ef0484d9c15451c4e7
0501e31da7608cb,0200e30565c3d7e02c822762f25db4c872811adb2cbfbad92b04
291bc8c476d0546d1c5ecf5c58ff06b8d19aad8eca9e5f1a80ff8e981ebc490b0cfb
d5d499b47bad8e
EvaluationElement = 0300abdee910f144c3be460e724c11626e1f9986f72e2c43
3a9c4dad2ef6fcb9249c9a5036334ba88b0892462b6f8ad419c38cc259b0c774a9bd
0c4d545d0914413ea2,02019696f91dcc178bbe6b97f822cdc4052f9b94852ff6023
f6068848f867df40e54a5f1525e7fafa383e82fe36bf3c74427b51903032d0f89876
05bf24ee003f37693
Proof = 008fa896b69c1efc4e9c6bdfd0b149444532d5ba3bfd957cf7cd71c374d3
a1cca25f17b60616164377b0734243bc878e17d3ecab36b3e3565b5c6218dae92d40
c0be018707381f6f4b0153044737030b5d9851c15609532da8932c1fa1f4901dba05
a4118d25142344f9ea1465c907eb13d908a45d8b98265eac48819a04cae859b0643a
ProofRandomScalar = 00d47b0d4ca4c64825ba085de242042b84d9ebe3b2e9de07
678ff96713dfe16f40f2c662a56ed2db95e1e7bf2dea02bd1fa76e953a630772f68b
53baade9962d1646
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
5,7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d1b2e936
669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c130110571
~~~
"#;
+604 -794
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
[formatting]
allowed_blank_lines = 1
reorder_keys = true