Compare commits

..
44 Commits
Author SHA1 Message Date
Kevin LewiandGitHub 829c6add0f Publishing 0.5.0-pre.3 (#107) 2023-03-04 15:09:02 -08:00
daxpeddaandGitHub 8b895cc631 Update RustCrypto dependencies to v0.13 (#106) 2023-03-04 14:54:13 -08:00
daxpeddaandGitHub 83eb78b232 Test P-384 (#84) 2023-02-08 15:48:12 -08:00
daxpeddaandGitHub 5badeff8d2 Update to draft 19 (#101) 2023-02-08 00:18:04 -08:00
Kevin LewiandGitHub 40d81294db Publishing 0.5.0-pre.2 (#104) 2023-02-03 13:26:11 -08:00
daxpeddaandGitHub 8363d26f6f Bump curve25519-dalek to v4.0.0-rc.1 (#102) 2023-02-03 11:19:00 -08:00
daxpeddaandGitHub 5bce3e3206 Use explicit crate features (#100) 2023-02-01 11:37:47 -08:00
daxpeddaandGitHub 2787151e1d Update curve25519-dalek (#94) 2023-01-31 14:19:48 -08:00
daxpeddaandGitHub 0409db6f40 Depend on ProjectivePoint: ToEncodedPoint (#95) 2023-01-31 14:19:33 -08:00
daxpeddaandGitHub 74eaebe446 Fix Clippy (#96) 2023-01-31 10:31:13 -08:00
daxpeddaandGitHub c8de51672b Replace json with serde_json (#92) 2023-01-19 14:17:49 -08:00
daxpeddaandGitHub daa8dc048f Upgrade p256 to v0.12 (#90)
* Upgrade `p256` to v0.12

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

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

* Fix CI complaints

* Address review comments, CHANGELOG entry, minor fixes
2022-07-09 07:30:20 -04:00
Kevin LewiandGitHub 6e16a99a87 Updating to draft version 10 (#79) 2022-07-01 12:35:21 -07:00
Kevin LewiandGitHub 4646fe4ad0 Exposing the derive_key() function under the danger feature (#73) 2022-04-05 00:41:38 -07:00
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
32 changed files with 5594 additions and 3749 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
+75 -21
View File
@@ -7,25 +7,47 @@ 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:
- ristretto255_u64
- ristretto255_u32
- p256,ristretto255_u64
- --features ristretto255-ciphersuite
-
frontend_feature:
- serde
- danger
-
- --features danger
- --features serde
toolchain:
- stable
- 1.51.0
- 1.65.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
@@ -38,13 +60,25 @@ jobs:
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 }}
- name: Run cargo test with all features enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
build-no-std:
name: Build with no-std on ${{ matrix.target }}
@@ -59,15 +93,13 @@ jobs:
- thumbv6m-none-eabi
backend_feature:
-
- --features ristretto255_u64
- --features ristretto255_u32
- --features p256
- --features ristretto255-ciphersuite
frontend_feature:
-
- --features serde
- --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 ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }}
@@ -78,7 +110,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
@@ -92,7 +124,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets -- -D warnings
args: --all-features --all-targets -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
@@ -100,21 +132,21 @@ jobs:
RUSTDOCFLAGS: -D warnings
with:
command: doc
args: --no-deps --document-private-items --features std,p256
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
@@ -123,3 +155,25 @@ jobs:
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
+29
View File
@@ -1,5 +1,34 @@
# Changelog
## 0.5.0-pre.3 (March 4, 2023)
* Updated to be in sync with draft-irtf-cfrg-voprf-19
* Increased MSRV to 1.65
* Updated p256 dependency to v0.13
* Added p384 tests
## 0.5.0-pre.2 (February 3, 2023)
* Increased MSRV to 1.60
* Updated p256 dependency to v0.12
* Updated curve25519-dalek dependency to 4.0.0-rc.1
## 0.5.0-pre.1 (December 19, 2022)
* Updated curve25519-dalek dependency to 4.0.0-pre.5
## 0.4.0 (September 15, 2022)
* Updated to be in sync with draft-irtf-cfrg-voprf-11, with
the addition of the POPRF mode
* Adds the evaluate() function to the servers to calculate the output of the OPRF
directly
* Renames the former evaluate() function to blind_evaluate to match the spec
* Fixes the order of parameters for PoprfClient::blind to align it with the
other clients
* Exposes the derive_key function under the "danger" feature
* Added support for running the API without performing allocations
* 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
+41 -31
View File
@@ -1,55 +1,65 @@
[package]
name = "voprf"
version = "0.3.0"
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
authors = ["Kevin Lewi <[email protected]>"]
repository = "https://github.com/novifinancial/voprf/"
authors = ["Kevin Lewi <[email protected]>"]
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/facebook/voprf/"
rust-version = "1.65"
version = "0.5.0-pre.3"
[features]
default = ["ristretto255_u64", "serde"]
alloc = []
danger = []
ristretto255_u64 = ["curve25519-dalek/u64_backend"]
ristretto255_u32 = ["curve25519-dalek/u32_backend"]
ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend"]
ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend"]
ristretto255_simd = ["curve25519-dalek/simd_backend"]
p256 = ["num-bigint", "num-integer", "num-traits", "once_cell", "p256_"]
std = []
serde = ["serde_", "base64"]
default = ["ristretto255-ciphersuite", "dep:serde"]
ristretto255 = ["dep:curve25519-dalek", "generic-array/more_lengths"]
ristretto255-ciphersuite = ["ristretto255", "dep:sha2"]
serde = ["generic-array/serde", "dep:serde"]
std = ["alloc"]
[dependencies]
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
curve25519-dalek = { version = "3", default-features = false, optional = true }
digest = "0.9"
curve25519-dalek = { version = "=4.0.0-rc.1", default-features = false, features = [
"rand_core",
"zeroize",
], optional = true }
derive-where = { version = "1", features = ["zeroize-on-drop"] }
digest = "0.10"
displaydoc = { version = "0.2", default-features = false }
elliptic-curve = { version = "0.13", features = [
"hash2curve",
"sec1",
"voprf",
] }
generic-array = "0.14"
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_core = { version = "0.6", default-features = false }
serde_ = { version = "1", package = "serde", default-features = false, optional = true }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", default-features = false }
zeroize = { version = "1.5", default-features = false }
[dev-dependencies]
generic-array = { version = "0.14", features = ["more_lengths"] }
hex = "0.4"
json = "0.12"
p256 = { version = "0.13", default-features = false, features = [
"hash2curve",
"voprf",
] }
p384 = { version = "0.13", default-features = false, features = [
"hash2curve",
"voprf",
] }
proptest = "1"
rand = "0.8"
regex = "1"
sha2 = "0.9"
voprf = { path = "", default-features = false, features = ["std", "danger"] }
serde_json = "1"
sha2 = "0.10"
[package.metadata.docs.rs]
features = ["danger", "p256", "std"]
targets = []
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = []
+2 -2
View File
@@ -16,12 +16,12 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.3"
voprf = "0.5.0-pre.3"
```
### Minimum Supported Rust Version
Rust **1.51** or higher.
Rust **1.65** 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
+48
View File
@@ -0,0 +1,48 @@
// 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.
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
use digest::core_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker, 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
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: &'static str;
/// A finite cyclic group along with a point representation that allows some
/// customization on how to hash an input to a curve point. See [`Group`].
type Group: Group;
/// The main hash function to use (for HKDF computations and hashing
/// transcripts).
type Hash: BlockSizeUser + Default + FixedOutput + HashMarker;
}
impl<T: VoprfParameters> CipherSuite for T
where
T: Group,
T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker,
<T::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
{
const ID: &'static str = T::ID;
type Group = T;
type Hash = T::Hash;
}
+575
View File
@@ -0,0 +1,575 @@
// 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 core::ops::Add;
use derive_where::derive_where;
use digest::core_api::BlockSizeUser;
use digest::{Digest, Output, OutputSizeUser};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U2, U256, U9};
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_OPRF: [u8; 7] = *b"OPRFV1-";
pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-";
pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-";
/// 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(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(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(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(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-11.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 = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_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-11.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 = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_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-11.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 = Dst::new::<CS, _, _>(STR_SEED, mode);
// h1Input = I2OSP(len(Bm), 2) || Bm ||
// I2OSP(len(seedDST), 2) || seedDST
// seed = Hash(h1Input)
let seed = CS::Hash::new()
.chain_update(elem_len)
.chain_update(CS::Group::serialize_elem(b))
.chain_update(seed_dst.i2osp_2())
.chain_update_multi(&seed_dst.as_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 = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_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 dst = Dst::new::<CS, _, _>(STR_DERIVE_KEYPAIR, mode);
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.as_dst(),
)
.map_err(|_| Error::DeriveKeyPair)?;
if !bool::from(CS::Group::is_zero_scalar(sk_s)) {
return Ok(sk_s);
}
}
Err(Error::Protocol)
}
/// Corresponds to DeriveKeyPair() function from the VOPRF specification.
///
/// # Errors
/// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer
/// then `u16::MAX - 3`.
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
#[cfg(feature = "danger")]
pub fn derive_key<CS: CipherSuite>(
seed: &[u8],
info: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Scalar, Error>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
derive_key_internal::<CS>(seed, info, mode)
}
type DeriveKeypairResult<CS> = (
<<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 hashed_point = hash_to_group::<CS>(input, mode)?;
Ok(hashed_point * blind)
}
/// Hashes `input` to a point on the curve
pub(crate) fn hash_to_group<CS: CipherSuite>(
input: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Elem>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
}
/// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF.
/// Returned values can only fail with [`Error::Input`].
pub(crate) fn server_evaluate_hash_input<CS: CipherSuite>(
input: &[u8],
info: Option<&[u8]>,
issued_element: GenericArray<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
) -> Result<Output<CS::Hash>>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
// OPRF & VOPRF
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(issuedElement), 2) || issuedElement ||
// "Finalize"
// return Hash(hashInput)
//
// POPRF
// hashInput = I2OSP(len(input), 2) || input ||
// I2OSP(len(info), 2) || info ||
// I2OSP(len(issuedElement), 2) || issuedElement ||
// "Finalize"
let mut hash = CS::Hash::new()
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(input.as_ref());
if let Some(info) = info {
hash = hash
.chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(info.as_ref());
}
Ok(hash
.chain_update(i2osp_2(issued_element.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(issued_element)
.chain_update(STR_FINALIZE)
.finalize())
}
pub(crate) struct Dst<L: ArrayLength<u8>> {
dst_1: GenericArray<u8, L>,
dst_2: &'static str,
}
impl<L: ArrayLength<u8>> Dst<L> {
pub(crate) fn new<CS: CipherSuite, T, TL: ArrayLength<u8>>(par_1: T, mode: Mode) -> Self
where
T: Into<GenericArray<u8, TL>>,
TL: Add<U9, Output = L>,
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let par_1 = par_1.into();
// Generates the contextString parameter as defined in
// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/>
let par_2 = GenericArray::from(STR_OPRF)
.concat([mode.to_u8()].into())
.concat([b'-'].into());
let dst_1 = par_1.concat(par_2);
let dst_2 = CS::ID;
assert!(
L::USIZE + dst_2.len() <= u16::MAX.into(),
"constructed DST longer then {}",
u16::MAX
);
Self { dst_1, dst_2 }
}
pub(crate) fn as_dst(&self) -> [&[u8]; 2] {
[&self.dst_1, self.dst_2.as_bytes()]
}
pub(crate) fn i2osp_2(&self) -> [u8; 2] {
u16::try_from(L::USIZE + self.dst_2.len())
.unwrap()
.to_be_bytes()
}
}
trait DigestExt {
fn chain_update_multi(self, data: &[&[u8]]) -> Self;
}
impl<T> DigestExt for T
where
T: Digest,
{
fn chain_update_multi(mut self, datas: &[&[u8]]) -> Self {
for data in datas {
self.update(data)
}
self
}
}
///////////////////////
// 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 {}
-41
View File
@@ -1,41 +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
#[cfg(feature = "std")]
use std::error::Error;
use displaydoc::Display;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Clone, Debug, 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,
}
#[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::{FixedOutput, HashMarker};
use elliptic_curve::group::cofactor::CofactorGroup;
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{
AffinePoint, Field, FieldBytesSize, 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>,
FieldBytesSize<Self>: ModulusSize,
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
Scalar<Self>: FromOkm,
{
type Elem = ProjectivePoint<Self>;
type ElemLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
type Scalar = Scalar<Self>;
type ScalarLen = FieldBytesSize<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: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
Self::hash_from_bytes::<ExpandMsgXmd<H>>(input, dst).map_err(|_| InternalError::Input)
}
// Implements the `HashToScalar()` function
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where
H: BlockSizeUser + Default + FixedOutput + HashMarker,
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_slice(scalar_bits)
.map(|secret_key| *secret_key.to_nonzero_scalar())
.map_err(|_| Error::Deserialization)
}
}
-214
View File
@@ -1,214 +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::util::i2osp;
use core::ops::Add;
use digest::{BlockInput, Digest};
use generic_array::{
sequence::Concat,
typenum::{Unsigned, U1, U2},
ArrayLength, GenericArray,
};
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor<L: ArrayLength<u8>>(x: GenericArray<u8, L>, y: GenericArray<u8, L>) -> GenericArray<u8, L> {
x.into_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<
'a,
H: BlockInput + Digest,
L: ArrayLength<u8>,
M: IntoIterator<Item = &'a [u8]>,
D: ArrayLength<u8> + Add<U1>,
>(
msg: M,
dst: GenericArray<u8, D>,
) -> Result<GenericArray<u8, L>, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let digest_len = <H as Digest>::OutputSize::USIZE;
let ell = div_ceil(L::USIZE, digest_len);
if ell > 255 {
return Err(InternalError::HashToCurveError);
}
let dst_prime = dst.concat(i2osp::<U1>(D::USIZE)?);
let z_pad = i2osp::<<H as BlockInput>::BlockSize>(0)?;
let l_i_b_str = i2osp::<U2>(L::USIZE)?;
let mut h = H::new();
// msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime
h.update(z_pad);
for bytes in msg {
h.update(bytes)
}
h.update(l_i_b_str);
h.update(i2osp::<U1>(0)?);
h.update(&dst_prime);
// b[0]
let b_0 = h.finalize_reset();
let mut b_i = GenericArray::default();
let mut uniform_bytes = GenericArray::default();
for (i, chunk) in (1..(ell + 1)).zip(uniform_bytes.chunks_mut(digest_len)) {
h.update(xor(b_0.clone(), b_i.clone()));
h.update(i2osp::<U1>(i)?);
h.update(&dst_prime);
b_i = h.finalize_reset();
chunk.copy_from_slice(&b_i[..digest_len.min(chunk.len())]);
}
Ok(uniform_bytes)
}
#[cfg(test)]
mod tests {
use generic_array::{
typenum::{U128, U32},
GenericArray,
};
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 = GenericArray::from(*b"QUUX-V01-CS02-with-expander");
for tv in test_vectors {
let uniform_bytes = match tv.len_in_bytes {
32 => super::expand_message_xmd::<sha2::Sha256, U32, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.map(|bytes| bytes.to_vec()),
128 => super::expand_message_xmd::<sha2::Sha256, U128, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.map(|bytes| bytes.to_vec()),
_ => unimplemented!(),
}
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+82 -118
View File
@@ -7,149 +7,113 @@
//! Defines the Group trait to specify the underlying prime order group
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
feature = "p256",
))]
mod expand;
#[cfg(feature = "p256")]
mod p256;
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
mod elliptic_curve;
#[cfg(feature = "ristretto255")]
mod ristretto;
use crate::errors::InternalError;
use core::ops::{Add, Mul, Sub};
use digest::{BlockInput, Digest};
use generic_array::{typenum::U1, ArrayLength, GenericArray};
use digest::core_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
#[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
+ ConstantTimeEq
+ 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: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// The type of base field scalars
type Scalar: Zeroize
pub trait Group {
/// The type of group elements
type Elem: ConstantTimeEq
+ Copy
+ ConstantTimeEq
+ 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<'a>(
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
) -> Result<Self::Scalar, InternalError> {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
if scalar.ct_eq(&Self::scalar_zero()).into() {
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<'a>(
element_bits: impl Into<&'a GenericArray<u8, Self::ElemLen>>,
) -> Result<Self, InternalError> {
let elem = Self::from_element_slice_unchecked(element_bits.into())?;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;
if Self::ct_eq(&elem, &<Self as Group>::identity()).into() {
// 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: BlockSizeUser + Default + FixedOutput + HashMarker,
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: BlockSizeUser + Default + FixedOutput + HashMarker,
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_eq(&<Self as Group>::identity()).into()
}
/// 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;
/// Set the contents of self to the identity value
fn zeroize(&mut self) {
*self = <Self as Group>::identity();
}
/// Serializes a scalar to bytes
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
/// 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)]
-580
View File
@@ -1,580 +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 core::ops::{Add, Div, Mul, Neg};
use core::str::FromStr;
use digest::{BlockInput, Digest};
use generic_array::typenum::{Unsigned, U1, U2, U32, U33, U48};
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::Field;
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable};
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `L: 48`
pub type L = U48;
#[cfg(feature = "p256")]
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: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// 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, <L as Mul<U2>>::Output, _, _>(Some(msg), dst)?;
// hash to curve
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L::USIZE], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L::USIZE..], &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
fn hash_to_scalar<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// 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: Lazy<BigInt> = 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, L, _, _>(input, dst)?;
let bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
.mod_floor(&N)
.to_bytes_be()
.1;
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(&bytes);
Ok(p256_::Scalar::from_bytes_reduced(&result))
}
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 bytes = self.to_affine().to_encoded_point(true);
let bytes = bytes.as_bytes();
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(bytes);
result
}
fn base_point() -> Self {
Self::generator()
}
fn identity() -> Self {
Self::identity()
}
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
}
/// 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())
}
}
/// 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> 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.inv0()
}
}
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 {
number: self.number.modpow(&exponent, self.f.0),
f: self.f,
}
}
/// 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()
}
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
fn inv0(&self) -> Self {
self.pow_internal(&(self.f.0 - 2))
}
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 bytes = self.number.to_bytes_be().1;
let mut result = GenericArray::default();
result[N::USIZE - bytes.len()..].copy_from_slice(&bytes);
result
}
}
fn cmov<'a>(x: &FieldElement<'a>, y: &FieldElement<'a>, b: bool) -> FieldElement<'a> {
let f = x.f;
let x_bytes = x.number.to_bytes_le().1;
let mut x = [0; 32];
x[..x_bytes.len()].copy_from_slice(&x_bytes);
let y_bytes = y.number.to_bytes_le().1;
let mut y = [0; 32];
y[..y_bytes.len()].copy_from_slice(&y_bytes);
let mut bytes = [0; 32];
let choice = Choice::from(u8::from(b));
for ((byte, x), y) in bytes.iter_mut().zip(&x).zip(&y) {
*byte = u8::conditional_select(x, y, choice);
}
FieldElement {
f,
number: BigInt::from_bytes_le(Sign::Plus, &bytes),
}
}
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 = x1.inv0();
// 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::*;
use generic_array::typenum::U96;
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 = GenericArray::from(*b"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, U96, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.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()));
}
}
}
+87 -90
View File
@@ -5,130 +5,127 @@
// 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 core::convert::TryInto;
use core::ops::Add;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use digest::{BlockInput, Digest};
use generic_array::{
typenum::{U1, U32, U64},
ArrayLength, GenericArray,
};
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::{FixedOutput, HashMarker};
use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
/// The implementation of such a subgroup for Ristretto
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
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)]
pub struct Ristretto255;
#[cfg(feature = "ristretto255-ciphersuite")]
impl crate::CipherSuite for Ristretto255 {
const ID: &'static str = "ristretto255-SHA512";
type Group = Ristretto255;
type Hash = sha2::Sha512;
}
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: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(Some(msg), dst)?;
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<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(input, dst)?;
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> {
CompressedRistretto::from_slice(element_bits)
.map_err(|_| Error::Deserialization)?
.decompress()
.filter(|point| point != &RistrettoPoint::identity())
.ok_or(Error::Deserialization)
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = {
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
};
let scalar = Scalar::random(rng);
if scalar != Scalar::zero() {
if scalar != Scalar::ZERO {
break scalar;
}
}
}
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 deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
scalar_bits
.try_into()
.ok()
.and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
.filter(|scalar| scalar != &Scalar::ZERO)
.ok_or(Error::Deserialization)
}
}
+24 -21
View File
@@ -7,44 +7,47 @@
//! Includes a series of tests for the group implementations
use crate::errors::InternalError;
use crate::group::Group;
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 curve25519_dalek::ristretto::RistrettoPoint;
fn test_group_properties() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
test_identity_element_error::<RistrettoPoint>()?;
test_zero_scalar_error::<RistrettoPoint>()?;
#[cfg(feature = "p256")]
#[cfg(feature = "ristretto255")]
{
use p256_::ProjectivePoint;
use crate::Ristretto255;
test_identity_element_error::<ProjectivePoint>()?;
test_zero_scalar_error::<ProjectivePoint>()?;
test_identity_element_error::<Ristretto255>()?;
test_zero_scalar_error::<Ristretto255>()?;
}
test_identity_element_error::<NistP256>()?;
test_zero_scalar_error::<NistP256>()?;
test_identity_element_error::<NistP384>()?;
test_zero_scalar_error::<NistP384>()?;
Ok(())
}
// Checks that the identity element cannot be deserialized
fn test_identity_element_error<G: Group>() -> Result<(), InternalError> {
let identity = G::identity();
let result = G::from_element_slice(&identity.to_arr());
assert!(matches!(result, Err(InternalError::PointError)));
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<G: Group>() -> Result<(), InternalError> {
let zero_scalar = G::scalar_zero();
let result = G::from_scalar_slice(&G::scalar_as_bytes(zero_scalar));
assert!(matches!(result, Err(InternalError::ZeroScalarError)));
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(())
}
-153
View File
@@ -1,153 +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.
/// Implement multiple similar traits at the same time. Additionally used to
/// find `#[bind]` markers to build `while` constraint.
macro_rules! impl_with_bounds {
(
$name:ident$(<$($gen:ident$(: $bound1:tt $(+ $bound2:tt)*)?),+>)?
// only collect types marked with `#bind`
// `|` prevents error about a possibly empty token
// `@` prevents ambiguity between `$_2` and `$trait1`
// `#` prevents ambiguity between marker traits and `$_2`
$(|$(@#bind: $type:ty|,)? $(@#pd: $_1:ty|,)? $(@$_2:ty|,)?)+
$trait1:path => { $($fn1:item)? },
$($trait2:path => { $($fn2:item)? },)*
) => {
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? $trait1 for $name$(<$($gen),+>)?
where
$($($type: $trait1,)?)+
{
$($fn1)?
}
impl_with_bounds!(
$name$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)?
$(|$(@#bind: $type|,)? $(@#pd: $_1|,)? $(@$_2|,)?)+
$($trait2 => { $($fn2)? },)*
);
};
// signature triggered when all traits are exhausted
(
$name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+>)?
$(|$(@#bind: $type:ty|,)? $(@#pd: $_1:ty|,)? $(@$_2:ty|,)?)+
) => { };
}
/// Skips attempt to call [`zeroize()`](zeroize::Zeroize::zeroize) on
/// [`PhantomData`](core::marker::PhantomData).
macro_rules! impl_internal_zeroize {
($self_:ident, #pd $field:ident) => {};
($self_:ident, #bind $field:ident) => {
$self_.$field.zeroize();
};
($self_:ident, $field:ident) => {
$self_.$field.zeroize();
};
}
macro_rules! impl_traits_for {
(
// include documentation, Rust can't connect documentation from outside
// a macro to a `struct` generated by a macro
$(#[doc = $doc:literal])*
$vis:vis struct $name:ident$(<$($gen:ident$(: $bound1:tt $(+ $bound2:tt)*)?),+$(,)?>)? {
$(#[$attr1:ident])? $vis1:vis $field1:ident: $type1:ty$(,
$(#[$attr2:ident])? $vis2:vis $field2:ident: $type2:ty)*$(,)?
}
) => {
// build `struct` itself
$(#[doc = $doc])*
$vis struct $name$(<$($gen$(: $bound1 $(+$bound2)*)?),+>)? {
$vis1 $field1: $type1,
$($vis2 $field2: $type2),*
}
// implement traits that require specific `where` constraints with the
// help of `#[bind]`
impl_with_bounds!(
$name$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)?
|@$(#$attr1:)? $type1|, $(|@$(#$attr2:)? $type2|,)*
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()
}
},
Eq => { },
PartialEq => {
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
},
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);)*
}
},
Clone => {
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
}
}
},
);
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)?
{
fn zeroize(&mut self) {
impl_internal_zeroize!(self, $(#$attr1)? $field1);
$(impl_internal_zeroize!(self, $(#$attr2)? $field2);)*
}
}
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? Drop for $name$(<$($gen),+>)?
{
fn drop(&mut self) {
zeroize::Zeroize::zeroize(self);
}
}
#[cfg(feature = "serde")]
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? serde_::Serialize for $name$(<$($gen),+>)? {
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 = "serde")]
impl<'de, $($($gen$(: $bound1 $(+ $bound2)*)?),+)?> serde_::Deserialize<'de> for $name$(<$($gen),+>)? {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde_::Deserializer<'de>,
{
use serde_::de::Error;
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
Self::deserialize(&base64::decode(s).map_err(Error::custom)?)
} else {
Self::deserialize(<&[u8]>::deserialize(deserializer)?)
}
.map_err(Error::custom)
}
}
};
}
+394 -238
View File
@@ -8,268 +8,348 @@
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
//!
//! Note: This implementation is in sync with
//! [draft-irtf-cfrg-voprf-08](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-08.html),
//! [draft-irtf-cfrg-voprf-19](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.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:
//!
//! ```
//! 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 and server
//! evaluation.
//!
//! ## 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.
//!
//! ```
//! # 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::<Group, Hash>::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
//! step of the VOPRF protocol.
//! In the first step, the client chooses an input, and runs [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.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &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
//! ### Server Blind 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::blind_evaluate] to produce [EvaluationElement] to be sent to
//! the client.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! let server_evaluate_result = server.evaluate(
//! client_blind_result.message,
//! None,
//! ).expect("Unable to perform server evaluate");
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let server_evaluate_result = server.blind_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 an output for the protocol.
//! In the final step on the client side, the client takes as input the message
//! from [OprfServer::evaluate] (an [EvaluationElement]), and runs
//! [OprfClient::finalize] to produce an output for the protocol.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # client_blind_result.message,
//! # None,
//! # ).expect("Unable to perform server evaluate");
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! None,
//! ).expect("Unable to perform client finalization");
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let message = server.blind_evaluate(&client_blind_result.message);
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(b"input", &message)
//! .expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ```
//!
//! ### Server Evaluation
//!
//! Optionally, if the server has direct access to the PRF input, then it need
//! not perform the oblivious computation and can simply run
//! [OprfServer::evaluate] to generate an output which matches the output
//! produced by an execution of the oblivious protocol on the same input and
//! key.
//!
//! ```
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::OprfServer;
//! # let mut server_rng = OsRng;
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let message = server.blind_evaluate(&client_blind_result.message);
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(b"input", &message)
//! .expect("Unable to perform client finalization");
//!
//! let server_evaluate_result = server
//! .evaluate(b"input")
//! .expect("Unable to perform the server evaluation");
//!
//! assert_eq!(client_finalize_result, server_evaluate_result);
//! ```
//!
//! ## Verifiable Mode
//!
//! 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.
//!
//! ```
//! # 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::<Group, Hash>::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.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &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
//! ### Server Blind 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::blind_evaluate] to produce a [VoprfServerEvaluateResult],
//! which consists of an [EvaluationElement] to be sent to the client along with
//! a proof.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! let server_evaluate_result = server.evaluate(
//! &mut server_rng,
//! client_blind_result.message,
//! None,
//! ).expect("Unable to perform server evaluate");
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerEvaluateResult { message, proof } =
//! server.blind_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 an output for the protocol.
//! [VoprfServer::blind_evaluate] (an [EvaluationElement]), the proof, and the
//! server's public key, and runs [VoprfClient::finalize] to produce an output
//! for the protocol.
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let server_evaluate_result = server.blind_evaluate(
//! # &mut server_rng,
//! # client_blind_result.message,
//! # None,
//! # ).expect("Unable to perform server evaluate");
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! server_evaluate_result.proof,
//! server.get_public_key(),
//! 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.to_vec());
//! ```
//!
//! ### Server Evaluation
//!
//! Optionally, if the server has direct access to the PRF input, then it need
//! not perform the oblivious computation and can simply run
//! [VoprfServer::evaluate] to generate an output which matches the output
//! produced by an execution of the oblivious protocol on the same input and
//! key.
//!
//! ```
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VoprfServer;
//! # let mut server_rng = OsRng;
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! # let server_evaluate_result = server.blind_evaluate(
//! # &mut server_rng,
//! # &client_blind_result.message,
//! # );
//! let client_finalize_result = client_blind_result
//! .state
//! .finalize(
//! b"input",
//! &server_evaluate_result.message,
//! &server_evaluate_result.proof,
//! server.get_public_key(),
//! )
//! .expect("Unable to perform client finalization");
//!
//! let server_evaluate_result = server
//! .evaluate(b"input")
//! .expect("Unable to perform the server evaluation");
//!
//! assert_eq!(client_finalize_result, server_evaluate_result);
//! ```
//!
//! # Advanced Usage
//!
//! There are two additional (and optional) extensions to the core VOPRF
@@ -278,157 +358,209 @@
//!
//! ## 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:
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &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_blind_evaluate_prepare] and
//! [VoprfServer::batch_blind_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:
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::VerifiableServer;
//! # use voprf::VoprfServer;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! let server_batch_evaluate_result = server.batch_evaluate(
//! &mut server_rng,
//! &client_messages,
//! None,
//! ).expect("Unable to perform server batch evaluate");
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let prepared_evaluation_elements = server.batch_blind_evaluate_prepare(client_messages.iter());
//! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
//! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
//! .batch_blind_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, 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_blind_evaluate` can be called
//! to avoid having to collect output manually:
//!
//! ```
//! # 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::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # 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::VerifiableServer;
//! # use voprf::VoprfServer;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_batch_evaluate_result = server.batch_evaluate(
//! # &mut server_rng,
//! # &client_messages,
//! # None,
//! # ).expect("Unable to perform server batch evaluate");
//! let client_batch_finalize_result = VerifiableClient::batch_finalize(
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
//! let VoprfServerBatchEvaluateResult { messages, proof } = server
//! .batch_blind_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_blind_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,
//! &server_batch_evaluate_result.messages,
//! server_batch_evaluate_result.proof,
//! &messages,
//! &proof,
//! server.get_public_key(),
//! 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);
//! # }
//! ```
//!
//! ## 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 blind 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.
//!
//! A custom metadata can be specified, for example, by: `Some(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-11.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 [Group](group::Group) 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 `serde` 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 `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 `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 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 `ristretto255_u64` feature is included as the default.
//! Other features are mapped as `ristretto255_u32`, `ristretto255_fiat_u64` and `ristretto255_fiat_u32`.
//! - The `ristretto255-ciphersuite` features enables using [`Ristretto255`] as
//! a [`CipherSuite`].
//!
//! - The `ristretto255_simd` feature is re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and enables parallel formulas,
//! using either AVX2 or AVX512-IFMA. This will automatically enable the `ristretto255_u64` feature and requires Rust nightly.
//! - The `ristretto255` feature enables using [`Ristretto255`] as the
//! underlying group for the [Group] choice. To select a specific backend see
//! the [curve25519-dalek] documentation.
//!
//! [curve25519-dalek]:
//! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends)
#![deny(unsafe_code)]
#![warn(clippy::cargo, missing_docs)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(test), deny(unsafe_code))]
#![warn(
clippy::cargo,
clippy::missing_errors_doc,
missing_debug_implementations,
missing_docs
)]
#![allow(clippy::multiple_crate_versions)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(any(feature = "alloc", test))]
extern crate alloc;
#[macro_use]
mod impls;
#[macro_use]
mod util;
pub mod errors;
pub mod group;
#[cfg(feature = "std")]
extern crate std;
mod ciphersuite;
mod common;
mod error;
mod group;
mod oprf;
mod poprf;
mod serialization;
mod voprf;
@@ -437,8 +569,32 @@ mod tests;
// Exports
pub use crate::voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableClientBlindResult,
NonVerifiableServer, NonVerifiableServerEvaluateResult, VerifiableClient,
VerifiableClientBlindResult, VerifiableServer, VerifiableServerEvaluateResult,
pub use crate::ciphersuite::CipherSuite;
#[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,
};
+458
View File
@@ -0,0 +1,458 @@
// 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, hash_to_group, i2osp_2,
server_evaluate_hash_input, 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(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(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 blind_evaluate(&self, blinded_element: &BlindedElement<CS>) -> EvaluationElement<CS> {
EvaluationElement(blinded_element.0 * &self.sk)
}
/// Computes the output of the OPRF on the server side
///
/// # Errors
/// [`Error::Input`] if the `input` is longer then [`u16::MAX`].
pub fn evaluate(&self, input: &[u8]) -> Result<Output<<CS as CipherSuite>::Hash>> {
let input_element = hash_to_group::<CS>(input, Mode::Oprf)?;
if CS::Group::is_identity_elem(input_element).into() {
return Err(Error::Input);
};
let evaluated_element = input_element * &self.sk;
let issued_element = CS::Group::serialize_elem(evaluated_element);
server_evaluate_hash_input::<CS>(input, None, issued_element)
}
}
/////////////////////////
// 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 rand::rngs::OsRng;
use super::*;
use crate::common::{Dst, 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 = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
let res = point * &key;
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.blind_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 = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst.as_dst()).unwrap();
let res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[])
.next()
.unwrap()
.unwrap();
assert_eq!(client_finalize_result, res2);
}
fn server_evaluate<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let mut rng = OsRng;
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = OprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server.blind_evaluate(&client_blind_result.message);
let client_finalize = client_blind_result
.state
.finalize(input, &server_result)
.unwrap();
// We expect the outputs from client and server to be equal given an identical
// input
let server_evaluate = server.evaluate(input).unwrap();
assert_eq!(client_finalize, server_evaluate);
// We expect the outputs from client and server to be different given different
// inputs
let wrong_input = b"wrong input";
let server_evaluate = server.evaluate(wrong_input).unwrap();
assert!(client_finalize != server_evaluate);
}
fn zeroize_oprf_client<CS: CipherSuite>()
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.blind_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;
use p384::NistP384;
#[cfg(feature = "ristretto255")]
{
use crate::Ristretto255;
base_retrieval::<Ristretto255>();
base_inversion_unsalted::<Ristretto255>();
server_evaluate::<Ristretto255>();
zeroize_oprf_client::<Ristretto255>();
zeroize_oprf_server::<Ristretto255>();
}
base_retrieval::<NistP256>();
base_inversion_unsalted::<NistP256>();
server_evaluate::<NistP256>();
zeroize_oprf_client::<NistP256>();
zeroize_oprf_server::<NistP256>();
base_retrieval::<NistP384>();
base_inversion_unsalted::<NistP384>();
server_evaluate::<NistP384>();
zeroize_oprf_client::<NistP384>();
zeroize_oprf_server::<NistP384>();
Ok(())
}
}
+998
View File
@@ -0,0 +1,998 @@
// 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::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use crate::common::{
derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2,
server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode,
PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO,
};
#[cfg(feature = "serde")]
use crate::serialization::serde::{Element, Scalar};
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(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(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>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> 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 blind_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_blind_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_blind_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_blind_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_blind_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_blind_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_blind_evaluate` without
/// memory allocation. Returned [`PreparedEvaluationElement`] have to
/// be [`collect`](Iterator::collect)ed and passed into
/// [`batch_blind_evaluate_finish`](Self::batch_blind_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_blind_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_blind_evaluate_prepare`](Self::batch_blind_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_blind_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 })
}
/// Computes the output of the VOPRF on the server side
///
/// # Errors
/// [`Error::Input`] if the `input` is longer then [`u16::MAX`].
pub fn evaluate(
&self,
input: &[u8],
info: Option<&[u8]>,
) -> Result<Output<<CS as CipherSuite>::Hash>> {
let input_element = hash_to_group::<CS>(input, Mode::Poprf)?;
if CS::Group::is_identity_elem(input_element).into() {
return Err(Error::Input);
};
let tweak = compute_tweak::<CS>(self.sk, info)?;
let evaluated_element = input_element * &CS::Group::invert_scalar(tweak);
let issued_element = CS::Group::serialize_elem(evaluated_element);
server_evaluate_hash_input::<CS>(input, info, issued_element)
}
/// Retrieves the server's public key
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(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 = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
// This can't fail, the size of the `input` is known.
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_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 = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
// This can't fail, the size of the `input` is known.
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_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 = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
// evaluatedElement = G.ScalarInverse(t) * blindedElement
let res = point * &CS::Group::invert_scalar(t);
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(input, &mut rng).unwrap();
let server_result = server
.blind_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(input, &mut rng).unwrap();
let server_result = server
.blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
.unwrap();
let wrong_pk = {
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, Mode::Oprf);
// Choose a group element that is unlikely to be the right public key
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst.as_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 verifiable_server_evaluate<CS: CipherSuite>()
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
let input = b"input";
let info = Some(b"info".as_slice());
let mut rng = OsRng;
let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
let server_result = server
.blind_evaluate(&mut rng, &client_blind_result.message, info)
.unwrap();
let client_finalize = client_blind_result
.state
.finalize(
input,
&server_result.message,
&server_result.proof,
server.get_public_key(),
info,
)
.unwrap();
// We expect the outputs from client and server to be equal given an identical
// input
let server_evaluate = server.evaluate(input, info).unwrap();
assert_eq!(client_finalize, server_evaluate);
// We expect the outputs from client and server to be different given different
// inputs
let wrong_input = b"wrong input";
let server_evaluate = server.evaluate(wrong_input, info).unwrap();
assert!(client_finalize != server_evaluate);
}
fn zeroize_verifiable_client<CS: CipherSuite>()
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(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_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(input, &mut rng).unwrap();
let server_result = server
.blind_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;
use p384::NistP384;
#[cfg(feature = "ristretto255")]
{
use crate::Ristretto255;
verifiable_retrieval::<Ristretto255>();
verifiable_bad_public_key::<Ristretto255>();
verifiable_server_evaluate::<Ristretto255>();
zeroize_verifiable_client::<Ristretto255>();
zeroize_verifiable_server::<Ristretto255>();
}
verifiable_retrieval::<NistP256>();
verifiable_bad_public_key::<NistP256>();
verifiable_server_evaluate::<NistP256>();
zeroize_verifiable_client::<NistP256>();
zeroize_verifiable_server::<NistP256>();
verifiable_retrieval::<NistP384>();
verifiable_bad_public_key::<NistP384>();
verifiable_server_evaluate::<NistP384>();
zeroize_verifiable_client::<NistP384>();
zeroize_verifiable_server::<NistP384>();
Ok(())
}
}
+369 -118
View File
@@ -5,188 +5,439 @@
// 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::{
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 core::marker::PhantomData;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
//////////////////////////////////////////////////////////
// Serialization and Deserialization for High-Level API //
// ==================================================== //
//////////////////////////////////////////////////////////
impl<G: Group, H: BlockInput + Digest> NonVerifiableClient<G, H> {
/// 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> {
[G::scalar_as_bytes(self.blind).as_slice(), &self.data].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 = <G 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 = G::from_scalar_slice(&input[..scalar_len])?;
let data = input[scalar_len..].to_vec();
Ok(Self {
blind,
data,
hash: PhantomData,
})
Ok(Self { blind })
}
}
impl<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
/// 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> {
[
G::scalar_as_bytes(self.blind).as_slice(),
&self.blinded_element.to_arr(),
&self.data,
]
.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 = <G as Group>::ScalarLen::USIZE;
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() < scalar_len + elem_len {
return Err(InternalError::SizeError);
}
let blind = G::from_scalar_slice(&input[..scalar_len])?;
let blinded_element = G::from_element_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,
hash: PhantomData,
})
}
}
impl<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
/// 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> {
G::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 = <G as Group>::ScalarLen::USIZE;
if input.len() != scalar_len {
return Err(InternalError::SizeError);
}
let sk = G::from_scalar_slice(input)?;
///
/// # 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 {
sk,
hash: PhantomData,
blind,
blinded_element,
})
}
}
impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
/// 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) -> Vec<u8> {
[G::scalar_as_bytes(self.sk).as_slice(), &self.pk.to_arr()].concat()
pub fn serialize(&self) -> GenericArray<u8, OprfServerLen<CS>> {
CS::Group::serialize_scalar(self.sk)
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <G as Group>::ScalarLen::USIZE;
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != scalar_len + elem_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 sk = G::from_scalar_slice(&input[..scalar_len])?;
let pk = G::from_element_slice(&input[scalar_len..])?;
Ok(Self {
sk,
pk,
hash: PhantomData,
})
Ok(Self { sk })
}
}
impl<G: Group, H: BlockInput + Digest> Proof<G, H> {
/// 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> {
[
G::scalar_as_bytes(self.c_scalar),
G::scalar_as_bytes(self.s_scalar),
]
.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 = <G as Group>::ScalarLen::USIZE;
if input.len() != scalar_len + scalar_len {
return Err(InternalError::SizeError);
}
Ok(Proof {
c_scalar: G::from_scalar_slice(&input[..scalar_len])?,
s_scalar: G::from_scalar_slice(&input[scalar_len..])?,
hash: PhantomData,
})
///
/// # 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<G: Group, H: BlockInput + Digest> BlindedElement<G, H> {
/// 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> {
self.value.to_arr().to_vec()
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 elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != elem_len {
return Err(InternalError::SizeError);
}
Ok(Self {
value: G::from_element_slice(input)?,
hash: PhantomData,
})
///
/// # 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<G: Group, H: BlockInput + Digest> EvaluationElement<G, H> {
/// 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) -> Vec<u8> {
self.value.to_arr().to_vec()
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
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != elem_len {
return Err(InternalError::SizeError);
}
Ok(Self {
value: G::from_element_slice(input)?,
hash: PhantomData,
})
///
/// # 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;
}
let (front, back) = self.split_at(take);
*self = back;
Some(front)
}
}
#[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)
}
}
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)
}
}
}
#[cfg(test)]
mod test {
use proptest::collection::vec;
use proptest::prelude::*;
use crate::{
BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof,
VoprfClient, VoprfServer,
};
macro_rules! test_deserialize {
($item:ident, $bytes:ident) => {
#[cfg(feature = "ristretto255")]
{
let _ = $item::<crate::Ristretto255>::deserialize(&$bytes[..]);
}
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
let _ = $item::<p384::NistP384>::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_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 }
}
@@ -51,7 +50,7 @@ impl RngCore for CycleRng {
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
dest[..len].copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
+2 -2
View File
@@ -5,7 +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;
mod test_cfrg_vectors;
+7 -5
View File
@@ -5,14 +5,16 @@
// 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))
}
fn parse_ciphersuites(input: &str) -> String {
let re = regex::Regex::new(r"## OPRF\((?P<ciphersuite>.+?)\)").unwrap();
let re = regex::Regex::new(r"\n## (?P<ciphersuite>.+?)\n").unwrap();
let mut ciphersuites = vec![];
let chunks: Vec<&str> = re.split(input).collect();
@@ -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);
@@ -94,7 +96,7 @@ fn parse_params(input: &str) -> String {
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
param = format!(" \"{}\": \"{}", key, val);
param = format!(" \"{key}\": \"{val}");
} else {
let s = line.trim().to_string();
if s.contains('~') || s.contains('#') {
+568
View File
@@ -0,0 +1,568 @@
// 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 serde_json::Value;
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: &Value) -> 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: &Value, key: &str) -> Vec<u8> {
values[key]
.as_str()
.and_then(|s| hex::decode(s).ok())
.unwrap_or_default()
}
fn decode_vec(values: &Value, key: &str) -> Vec<Vec<u8>> {
let s = values[key].as_str().unwrap();
let res = match s.contains(',') {
true => Some(s.split(',').map(|x| hex::decode(x).unwrap()).collect()),
false => Some(vec![hex::decode(s).unwrap()]),
};
res.unwrap()
}
macro_rules! json_to_test_vectors {
( $v:ident, $cs:expr, $mode:expr ) => {
$v[$cs][$mode]
.as_array()
.into_iter()
.flatten()
.map(populate_test_vectors)
.collect::<Vec<VOPRFTestVectorParameters>>()
};
}
#[test]
fn test_vectors() -> Result<()> {
use p256::NistP256;
use p384::NistP384;
let rfc: Value = serde_json::from_str(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-SHA512"),
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_blind_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_finalize::<Ristretto255>(&ristretto_oprf_tvs)?;
test_oprf_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
let ristretto_voprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255-SHA512"),
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_blind_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_finalize::<Ristretto255>(&ristretto_voprf_tvs)?;
test_voprf_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
let ristretto_poprf_tvs = json_to_test_vectors!(
rfc,
String::from("ristretto255-SHA512"),
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_blind_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_finalize::<Ristretto255>(&ristretto_poprf_tvs)?;
test_poprf_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
}
let p256_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("OPRF"));
assert_ne!(p256_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP256>(&p256_oprf_tvs)?;
test_oprf_blind::<NistP256>(&p256_oprf_tvs)?;
test_oprf_blind_evaluate::<NistP256>(&p256_oprf_tvs)?;
test_oprf_finalize::<NistP256>(&p256_oprf_tvs)?;
test_oprf_evaluate::<NistP256>(&p256_oprf_tvs)?;
let p256_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("VOPRF"));
assert_ne!(p256_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP256>(&p256_voprf_tvs)?;
test_voprf_blind::<NistP256>(&p256_voprf_tvs)?;
test_voprf_blind_evaluate::<NistP256>(&p256_voprf_tvs)?;
test_voprf_finalize::<NistP256>(&p256_voprf_tvs)?;
test_voprf_evaluate::<NistP256>(&p256_voprf_tvs)?;
let p256_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P256-SHA256"), String::from("POPRF"));
assert_ne!(p256_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP256>(&p256_poprf_tvs)?;
test_poprf_blind::<NistP256>(&p256_poprf_tvs)?;
test_poprf_blind_evaluate::<NistP256>(&p256_poprf_tvs)?;
test_poprf_finalize::<NistP256>(&p256_poprf_tvs)?;
test_poprf_evaluate::<NistP256>(&p256_poprf_tvs)?;
let p384_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("OPRF"));
assert_ne!(p384_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP384>(&p384_oprf_tvs)?;
test_oprf_blind::<NistP384>(&p384_oprf_tvs)?;
test_oprf_blind_evaluate::<NistP384>(&p384_oprf_tvs)?;
test_oprf_finalize::<NistP384>(&p384_oprf_tvs)?;
test_oprf_evaluate::<NistP384>(&p384_oprf_tvs)?;
let p384_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("VOPRF"));
assert_ne!(p384_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP384>(&p384_voprf_tvs)?;
test_voprf_blind::<NistP384>(&p384_voprf_tvs)?;
test_voprf_blind_evaluate::<NistP384>(&p384_voprf_tvs)?;
test_voprf_finalize::<NistP384>(&p384_voprf_tvs)?;
test_voprf_evaluate::<NistP384>(&p384_voprf_tvs)?;
let p384_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P384-SHA384"), String::from("POPRF"));
assert_ne!(p384_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP384>(&p384_poprf_tvs)?;
test_poprf_blind::<NistP384>(&p384_poprf_tvs)?;
test_poprf_blind_evaluate::<NistP384>(&p384_poprf_tvs)?;
test_poprf_finalize::<NistP384>(&p384_poprf_tvs)?;
test_poprf_evaluate::<NistP384>(&p384_poprf_tvs)?;
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_blind_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.blind_evaluate(&BlindedElement::deserialize(
&parameters.blinded_element[i],
)?);
assert_eq!(
&parameters.evaluation_element[i],
&message.serialize().as_slice()
);
}
}
Ok(())
}
fn test_voprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
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_blind_evaluate_prepare(blinded_elements.iter());
let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
.batch_blind_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_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
{
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_blind_evaluate_prepare(blinded_elements.iter(), Some(&parameters.info))?;
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
let PoprfServerBatchEvaluateFinishResult { messages, proof } =
PoprfServer::batch_blind_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(())
}
// Tests input, sksm -> output
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = OprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result = server.evaluate(&parameters.input[i])?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = VoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result = server.evaluate(&parameters.input[i])?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
where
<CS::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
{
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
let server_evaluate_result =
server.evaluate(&parameters.input[i], Some(&parameters.info))?;
assert_eq!(&parameters.output[i], &server_evaluate_result.to_vec());
}
}
Ok(())
}
-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::{
errors::InternalError,
group::Group,
tests::{mock_rng::CycleRng, parser::*},
voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
},
};
use alloc::string::ToString;
use alloc::vec::Vec;
use digest::{BlockInput, Digest};
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_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.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 curve25519_dalek::ristretto::RistrettoPoint;
use sha2::Sha512;
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::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_blind::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_evaluate::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_finalize::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_verifiable_seed_to_key::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_blind::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_evaluate::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_finalize::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
#[cfg(feature = "p256")]
{
use p256_::ProjectivePoint;
use sha2::Sha256;
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::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_blind::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_evaluate::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_finalize::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_verifiable_seed_to_key::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_blind::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_evaluate::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_finalize::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
}
Ok(())
}
fn test_base_seed_to_key<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = NonVerifiableServer::<G, H>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&G::scalar_as_bytes(server.get_private_key()).to_vec()
);
}
Ok(())
}
fn test_verifiable_seed_to_key<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = VerifiableServer::<G, H>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&G::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<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind =
G::from_scalar_slice(&GenericArray::clone_from_slice(&parameters.blind[i]))?;
let client_result = NonVerifiableClient::<G, H>::deterministic_blind_unchecked(
parameters.input[i].clone(),
blind,
)?;
assert_eq!(
&parameters.blind[i],
&G::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<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let blind =
G::from_scalar_slice(&GenericArray::clone_from_slice(&parameters.blind[i]))?;
let client_blind_result = VerifiableClient::<G, H>::deterministic_blind_unchecked(
parameters.input[i].clone(),
blind,
)?;
assert_eq!(
&parameters.blind[i],
&G::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<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = NonVerifiableServer::<G, H>::new_with_key(&parameters.sksm)?;
let server_result = server.evaluate(
BlindedElement::deserialize(&parameters.blinded_element[i])?,
Some(&parameters.info),
)?;
assert_eq!(
&parameters.evaluation_element[i],
&server_result.message.serialize()
);
}
}
Ok(())
}
fn test_verifiable_evaluate<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = VerifiableServer::<G, H>::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, Some(&parameters.info))?;
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<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let client = NonVerifiableClient::<G, H>::from_data_and_blind(
&parameters.input[i],
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
);
let client_finalize_result = client.finalize(
EvaluationElement::deserialize(&parameters.evaluation_element[i])?,
Some(&parameters.info),
)?;
assert_eq!(&parameters.output[i], &client_finalize_result.to_vec());
}
}
Ok(())
}
fn test_verifiable_finalize<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
let client = VerifiableClient::<G, H>::from_data_and_blind_and_element(
&parameters.input[i],
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
<G as Group>::from_element_slice(&GenericArray::clone_from_slice(
&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 = VerifiableClient::batch_finalize(
&clients,
&messages,
Proof::deserialize(&parameters.proof)?,
G::from_element_slice(GenericArray::from_slice(&parameters.pksm))?,
Some(&parameters.info),
)?;
assert_eq!(
parameters.output,
batch_result
.iter()
.map(|arr| arr.to_vec())
.collect::<Vec<Vec<u8>>>()
);
}
Ok(())
}
-637
View File
@@ -1,637 +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 = 74db8e13d2c5148a1181d57cc06debd730da4df1978b72ac18bc48992a0d2
c0f
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf
8e03
BlindedElement = 744441a5d3ee12571a84d34812443eba2b6521a47265ad655f0
1e759b3dd7d35
EvaluationElement = 4254c503ee2013262473eec926b109b018d699b8dd954ee8
78bc17b159696353
Output = 9aef8983b729baacb7ecf1be98d1276ca29e7d62dbf39bc595be018b66b
199119f18579a9ae96a39d7d506c9e00f75b433a870d76ba755a3e7196911fff89ff
3
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037
e50b
BlindedElement = f4eeea4e1bcb2ec818ee2d5c1fcec56c24064a9ff4bea5b3dd6
877800fc28e4d
EvaluationElement = 185dae43b6209dacbc41a62fd4889700d11eeeff4e83ffbc
72d54daee7e25659
Output = f556e2d83e576b4edc890472572d08f0d90d2ecc52a73b35b2a8416a72f
f676549e3a83054fdf4fd16fe03e03bee7bb32cbd83c7ca212ea0d03b8996c2c268b
2
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = ad08ad9c7107691d792d346d743e8a79b8f6ae0673d58cbf7389d7003598c
903
pkSm = 7a5627aec2f2209a2fc62f39f57a8f5ffc4bbfd679d0273e6081b2b621ee3
b52
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e326
3503
BlindedElement = 56c6926e940df23d5dfe6a48949c5a9e5b503df3bff36454ba4
821afa1528718
EvaluationElement = 523774950001072a4fb1f1f3300f7feb1eeddb5b8304baa9
c3d463c11e7f0509
Proof = c973c8cfbcdbb12a09e7640e44e45d85d420ed0539a18dc6c67c189b4f28
c70dd32f9b13717ee073e1e73333a7cb17545dd42ed8a2008c5dae11a3bd7e70260d
ProofRandomScalar = 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9d
bcec831b8c681a09
Output = 2d9ed987fdfa623a5b4d5e445b127e86212b7c8f2567c175b424c59602f
bba7c36975df5e4ecdf060430c8b1b581fc97e953535fd82089e15afbafcf310b339
9
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd4171
ea02
BlindedElement = 5cd133d03df2e1ff919ed85501319c2039853dd7dc59da73605
fd5791b835d23
EvaluationElement = c0ba1012cbfb0338dadb435ef1d910eb179dc18c0d0a341f
0249a3a9ff03b06e
Proof = 156761aee4eb6a5e1e32bc0adb56ea46d65883777e152d4c607a3a3b8abf
3b036ecebae005d3f26222a8da0a3924cceed8a1a7c707ef4ba077456c3e80f8c40f
ProofRandomScalar = 74ae06fd50d5f26c2519bd7b184f45dd3ef2cb50197d42df
9d013f7d6c312a0b
Output = f5da1276b5ca3de4591534cf2d96f7bb49059bd374f40259f42dca89d72
3cac69ed3ae567128aaa2dfdf777f333615524aec24bc77b0a38e200e6a07b6c638e
b
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 80513e77795feeec6d2c450589b0e1b178febd5c193a9fcba0d27f0a06e0
d50f,533c2e6d91c934f919ac218973be55ba0d7b234160a0d4cf3bddafbda99e2e0
c
BlindedElement = 1c7ee9c1b4145dabeba9ad159531432a20718cb44a86f79dc73
f6f8671c9bf5e,7c1ef37881602cb6d3cf995e6ee310ed51e39b80ce0a825a316bc6
21d0580a14
EvaluationElement = a8a66348d351408cb7e2d26341a1258ba91c1a7d1b380f62
15bdfc242500991b,5a4b72bee9d2ca80ea220571690e2f92fadd0c13635b2888bc1
ff255f8fee975
Proof = caad28bac17ce71d59b43956e8d80f3edde3d0c317144bef3d10d9733ef1
cf09fd910c663ea85ad7cfaf641d73314694fe18d3f6b89cfe001b18163ff908d10a
ProofRandomScalar = 3af5aec325791592eee4a8860522f8444c8e71ac33af5186
a9706137886dce08
Output = 2d9ed987fdfa623a5b4d5e445b127e86212b7c8f2567c175b424c59602f
bba7c36975df5e4ecdf060430c8b1b581fc97e953535fd82089e15afbafcf310b339
9,f5da1276b5ca3de4591534cf2d96f7bb49059bd374f40259f42dca89d723cac69e
d3ae567128aaa2dfdf777f333615524aec24bc77b0a38e200e6a07b6c638eb
~~~
## OPRF(decaf448, SHAKE-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 82c2a6492e1792e6ccdf1d7cff410c717681bd53ad47da7646b14ebd05885
53e4c034e02b3ae5e724600a17a638ad528c04f793df56c2618
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = d1080372f0fcf8c5eace50914e7127f576725f215cc7c111673c635ce668
bbbb9b50601ad89b358ab8c23ed0b6c9d040365ec9d060868714
BlindedElement = 1c354d6d31500c7c5ae6fb10901ac87552ea3af1824e79871e2
596ef537f86abac64859cf6f35911ab74f0b09a06ecc757a65a104e9e49fb
EvaluationElement = 9e5bbf27b2312a493b2f2f1d051b7cdf3801769ec5dc0724
51b68c4d0d4ed9303979ec4798261a01fabd8d25540f48a11dd8342fded95383
Output = 5f8c28d5e760786cbd000ac58444bd216141472b9370b058408a714da5e
3dd51fc572f96c99a9338bc8569abc991bc1523fa1467cd3a0de3aef7f154bd65d92
e
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = aed1ffa44fd8f0ed16373606a3cf7df589cca86d8ea1abbf5768771dbef3
d401c74ae55ba1e28b9565e1e4018eb261a14134a4ce60c1c718
BlindedElement = e8111f22d50595f68f01a6a9135f50e8702c90794c2637fbe00
9046f0c455884cc77ee7a87f3abf494afe780b3620ab0e7fb65c65ba902b2
EvaluationElement = 0ec625f99914ba702f0e6bc5d0f837cb4deaf7ab3ac55458
7182c3dfe1dad6d1540964f9581d26e8ef0a47b61c5f145109a5fffe04ad528e
Output = 7f0e40c08d8220f88c0961925f764ee0e4e08909d497f462a97a2030b40
b44986fa76d344efb9b0acab23db81356fc8c380b80701a61a5fa76097a5d2ea7aa9
e
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 5d295b55d1d6e46411bbb4151d154dc61711012ff2390255b3345988f8e3c
458089d52e9b1d837049898f9e4e63a4534f0ed3b3a47c7051c
pkSm = 8e623ef9b65ef2ce148ce56249ee5e69ed6acd3e504a07905cc4c09312551
8d30ae7d6de274438b822d5a55a4365216ac588a4c400fbf6ff
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 4c936db1779a621b6c71475ac3111fd5703a59b713929f36dfd1e892a7fe
814479c93d8b4b6e11d1f6fe5351e51457b665fa7b76074e531f
BlindedElement = 74bb2406b15a86ba94b0686901545f8ddc23e64918de47c76fa
0bf812387021392c73e01068ac9cc07c7647b3d0d4e648c27bb3880ddb8e5
EvaluationElement = 90997b495c19f16561a3286a7bcba9a4ee6e12bab4d580d5
004ae5064d90a389124e81066f3f1dbf9a729ab46ed674c3292f56d54a0d5641
Proof = 668f6ef88b249d51b6c94bfe82f2bec35ab7386bc9f3d14209d0247a5b6e
bedec4c333947fff96d322f516f4674cc07638b8e854c52be7045d83d65aff518104
60ec43417a6c6efbfb67ba7b0257b1237c64e6792195e338474d09df32b076c0b702
ec8c639b34c29878b87aad70d63c
ProofRandomScalar = 1b3f5a55b2f18f8c53d4ecf2e1c27e1028f1c345bb504486
4aa9dd8439d7520a7ba6183d50ef08bdf6c781aa465660c93e8195a8d231b62f
Output = 7db8c49354861f2d71c8175681c9cc930a00251330b2acc5c321f9833fe
d4113a1cb3e05a3840082c24e8d49470474dd1c7586f3663f32f66dc3888c63dc0e6
e
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 15b3355179392f40c3d5a15f0d5ffc354e340454ec779f575e4573a3886a
b5e57e4da2985cea9e32f6d95539ce2c7189e1bd7462e8c5483a
BlindedElement = ea3418614d71144ac4ecbd2c63c30ce34718b739ba0a5dd3585
efd9800b9debdad4cffc25dcc39b4691aaffba19ead8a425d7d50f016f57e
EvaluationElement = 7e12ab491c3787a1f17118f7a0308f8c41f4cd6e850cf7fa
ba030b6c1bf1888337149e7c2fc88068626a0107be18e8b9e29f41c8d1510049
Proof = 91ed184bf518a155749a99d39bed3f9dc9895054e55fab0ebd0ce4270e84
52fcc8da055e8c2f75f2306ecacaa594de592e0d0b059b8eb30e15d5c3132b71ebc4
933596c563ee8ce8681e0e40534e92ce487a0e33e341f02a9aaa1f750d9efa7545a0
008b2f8dde5047ce68d00c2e962e
ProofRandomScalar = 2f2e9955be83a4b25743ebd3618d4fad8b7288477da50bed
9befa58af639ddd950fec34205f8a4f166fadcb8fa71a3ffdd2e98f4c8ef5e26
Output = 66125718c5d651c88ab57dda67c52a506d436600f1521b7684c869b9a2b
3e67d1b41c47593e79fd6b70aaae8d3689536897ae8964ffcd433c0884c12c94929d
c
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 614bb578f29cc677ea9e7aea3e4839413997e020f9377b63c13584156a09
a46dd2a425c41eac0e313a47e99d05df72c6e1d58e6592577a0d,4c115060bca87db
7d73e00cbb8559f84cb7a221b235b0950a0ab553f03f10e1386abe954011b7da62bb
6599418ef90b5d4ea98cc28aff517
BlindedElement = 909b0b8bcb900bd9e70f27258d7264015c50f3717361afff22d
16ad84758d2c6b7a1963263d0d035f63b88df8b473f9365c53abcec34b201,726315
ee47e217344da7036a24f806177e221c9f6eae5763f9089b16bada69b85aec56c3ca
83b6f5f1091640ea3fe3e9429ff2aa7772efef
EvaluationElement = 46d8dec85a27698b4b69a67299eab1da0ec2bbed013a3a59
b932e2938e2e2c5bcc8274febf49b7903419c18b895f17c4a9a504737d7a3fdc,fe4
7eca9d06b400c80cc2b749284312c6f97c7b5d88055fe56b068c441e053fe909c6c2
2bb7cd646a932e2d3838b7b3e2e883cfe0ed1a2a1
Proof = 1f63637de4f945f5937ac015a508420f119f7b6a8e001439a1923a1705ce
ee704ad17664ff4c72f89566f83ceccee3001d44d849ac4dad2bc05b9bc718ba787f
c3c5b09198c4ab244455bac64a9a231b18c4682c0e6e30ae5398f5c041ee2c5b02c6
19b7497c5bf070fdb4656353de1d
ProofRandomScalar = a614f1894bcf6a1c7cef33909b794fe6e69a642b20f4c911
8febffaf6b6a31471fe7794aa77ced123f07e56cc27de60b0ab106c0b8eab127
Output = 7db8c49354861f2d71c8175681c9cc930a00251330b2acc5c321f9833fe
d4113a1cb3e05a3840082c24e8d49470474dd1c7586f3663f32f66dc3888c63dc0e6
e,66125718c5d651c88ab57dda67c52a506d436600f1521b7684c869b9a2b3e67d1b
41c47593e79fd6b70aaae8d3689536897ae8964ffcd433c0884c12c94929dc
~~~
## OPRF(P-256, SHA-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = c15d9e9ab36d495d9d62954db6aafe06d3edabf41600d58f9be0737af2719
e97
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 5d9e7f6efd3093c32ecceabd57fb03cf760c926d2a7bfa265babf29ec98a
f0d0
BlindedElement = 03e9097c54d2ea05f99424bdf984ea30ecc3614029bd5f1139e
70c4e1ae3bdbd92
EvaluationElement = 0202e4d1a338659c211900c39855f30025359928d261e6c9
558d667b3fbbc811cd
Output = 15b96275d06b85741f491fe0cad5cb835baa6c39066cbea73132dcf95e8
58e1c
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 825155ab61f17605af2ae2e935c78d857c9407bcd45128d57d338f1671b5
fcbe
BlindedElement = 03fa1ea45dd58d6b516c1252f2791610bf5ff1828c93be8af66
786f45fb4d14db5
EvaluationElement = 02657822553416d91bb3d707040fd0d5a0555f5cbae7519d
f3a297747a3ad1dd57
Output = e97f3f451f3cfce45a530dec0a0dec934cd78c5b656771549072ee236ce
070b9
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 7f62054fcd598b5e023c08ef0f04e05e26867438d5e355e846c9d8788d5c7
a12
pkSm = 03d6c3f69cfa683418a533fc52143a377f166e571ae50581abcb97ffd4e71
24395
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = cee64d86fd20ab4caa264a26c0e3d42fb773b3173ba76f9588c9b14779bd
8d91
BlindedElement = 029e103c4003ab9bf4a42e2003dd180922c8517927a68320058
178fee56c6ac8a0
EvaluationElement = 02856ac0748085d250d842b8b8fff6c1a9f688c961de52c4
a1e6c004c48196a123
Proof = 2a95bd827cf47873c886967ef6c17fe0e46efddd3b5f639927215cb7592a
4bf12a29117174a1af5899d64855352690e416b37f2a95580846a6bec445d82364fc
ProofRandomScalar = 70a5204b2b606f5a28328916e1e5ea5a17862d7a261fdd6d
959759758d5e34ac
Output = 14afc50acf64589445991da5b60add8b3f71205d53a983023d3cdaf8c95
c300d
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5c4b401063eff0bf242b4cd534a79bacfc2e715b2db1e7a3ad4ff8af1b24
daa2
BlindedElement = 0323aabcfa93e9570524253671b3ce083144b183cecb562ec8f
8a8472fc8cf341b
EvaluationElement = 03087bc7e00b8ad80b8a27484b91f8bf824a5d896a703135
4edfa3269866493d9f
Proof = fe55ecc9a92f940d4a56207a58e5554c6976b9425c917d24237b0a35c312
bdcdea778a5c56690309ff28f26cc8bc5994e85868e3c870e5a32c0a559d80deccb8
ProofRandomScalar = 3b9217801b5d51cef66d9fdbd94a53533e7c5057e09e2200
65ea8c257c0dd606
Output = 533c79459ee0ffa8844ac37572f3616e10a1074dcbf945ce37b0c651cbb
5775f
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = f0c7822ba317fb5e86028c44b92bd3aedcf6744d388ca013ef33edd36930
4eda,3b9631be9f8b274d9aaf671bfb6a775229bf435021b89c683259773bc686956
b
BlindedElement = 021af4563c31cf1513bc5ae0b89c5b527c7ac70614b9d31c44c
eb292ab49c91cc4,03f7e7ebe5610710c360df40cbd90dc52c2da500664e879f2afb
78e71f815abee1
EvaluationElement = 03c8678cdb95e2f0eac027932c51893a20326b774ef23531
bcd95def84060d240d,02b68c3891314a9696b5dff5df4b4e5b325938e2c5cb90f5f
b9ba6a1133aa4dd14
Proof = 6efbde69d36e3f9d53a79a73ce46d5d8ef31f0df2fb3f6f2c882b21fdf0e
d76dcd755e42f35f00daaa6e964f48125cf1d642b1cea2e5faa2fb868584a8752bf2
ProofRandomScalar = 8306b863276ae74049615162a416d507a6532c99c1ea3f03
d05f6e78dc1edabe
Output = 14afc50acf64589445991da5b60add8b3f71205d53a983023d3cdaf8c95
c300d,533c79459ee0ffa8844ac37572f3616e10a1074dcbf945ce37b0c651cbb577
5f
~~~
## OPRF(P-384, SHA-384)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = b9ff42e68ef6f8eaa3b4d15d15ceb6f3f36b9dc332a3473d64840fc7b4462
6c6e70336bdecbe01d9c512b7e7d7e6af21
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 359073c015b92d15450f7fb395bf52c6ea98384c491fe4e4d423b59de7b0
df382902c13bdc9993d3717bda68fc080b99
BlindedElement = 0285d803c65fda56993a296b99e8f4944e45cccb9b322bbc265
c91a21d2c9cd146212aefbf3126ed59d84c32d6ab823b66
EvaluationElement = 026061a4ccfe38777e725855c96570fe85303cd70567007e
489d0aa8bfced0e47579ecbc290e5150b9e84bf25188294f7e
Output = bc2c3c895f96d769703aec18359cbc0e84b41248559f0bd44f1e5467522
3c77e00874bbe61c1c320d3c95aee5a8c752f
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 21ece4f9b6ffd01ce82082545413bd9bb5e8f3c63b86ae88d9ce0530b01c
b1c23382c7ec9bdd6e75898e4877d8e2bc17
BlindedElement = 0211dd06e40b902006c33a92dc476a7c708b6b46c990656239c
d6867ff0be5867d859517eaf7ea9bad10702b80a9dc6bdc
EvaluationElement = 03a1d34b657f6267b29338592e3c769db5d3fc8713bf2eb7
238efb8138d5af8c56f9437315a5c58761b35cbfc0e1d2511d
Output = ee37530d0d7b20635fbc476317343b257750ffb3e83a2865ce2a46e5959
1f854b8301d6ca7d063322314a33b953c8bd5
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 42c4d1c15d27be015844404088967afe48c8ae96d4f00ce48e4d38ecabfb8
feb5b748de625cdf81ab076745d6211be95
pkSm = 0389ad5e50eebf9617ae3a5778e4f0665b56aa9066919e1fa5580d8dd781d
560824e0e78aae816af6eff8abe2ad0585a0d
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 102f6338df84c9602bfa9e7d690b1f7a173d07e6d54a419db4a6308f8b09
589e4283efb9cd1ee4061c6bf884e60a8774
BlindedElement = 02ae8990d580dcd52b6bc273bc6d0fd25be50b057511b953d9c
c95bb27cb3e1fd3249ae19744ed496c6e4104ebc1ed48f1
EvaluationElement = 024cffdae0cae5fa4d6a68246ae797dbe06508284b65e0f0
9046977ab5d52a8b38f0245607db74979e5276fc636332cdee
Proof = 128ad4f987ce1e3a9aab1e487df15d8c8000d5c4c9f14bd7fd699fabdb8d
a3f577d91625fabb0d9cf6069f8af6d9cc232dd63cd161be84a1e146e0110dc741e6
26a082193aa0a26e03118b662f1b903667f6e6fba51d69a2d65982a3b64ecb35
ProofRandomScalar = 90f67cafc0ffaa7a1e1d1ced3c477fea691e696032c8709c
86cbcda2b184ad0029d29abeabede9788d11782429bff297
Output = 8a0b4829bc8422b1a2301d5471256892883c5e3fe27b998d1010225a706
545637336a20a76f842d8a22e591d382c77e4
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 8aec1d0c3d16afd032da7ba961449a56cec6fb918e932b06d5778ac7f67b
ecfb3e3869237f74106241777f230582e84a
BlindedElement = 02a384f2d9635adffcc5482344c519036c019f3cc0918ec737c
67cdda10ac0f73a9fe348835531f1900ea2c1f06dacdce4
EvaluationElement = 0306f0f71b58d53ae0973538a7bf2ce8fba7143efc88d2ef
ca6cf1f98fb8399b16840d1fbbe7897807db930f67916418ae
Proof = 2c47297ee0093061ca2c87b430b2851a860aaae76c2bdba48779ba4294e7
de0556ede3e6b881a04970b68a6126e2fa197d69e6784fbbd173604501c0edd21696
628f0fd7cb13be28f94e5e15c042ffccadd780b2448d7d9d528e9615e4e70539
ProofRandomScalar = bb1876a7f7165ac7ec79bfd5213ea2e374252f29a6e19915
f81b0c7dcea93ce6580e089ede31c1b6b5b33494581b4868
Output = 8c52d40c1f6cc80208bd610178a5034d6c4a05584e19b69617f846b09a8
545443c63c8aa4d85bf0aad368e0591b1216a
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 41fabd4722d92472d858051ce9ad1a533176a862c697b2c392aff2aeb77e
b20c2ae6ba52fe31e13e03bf1d9f39878b23,51171628f1d28bb7402ca4aea6465e2
67b7f977a1fb71593281099ef2625644aee0b6c5f5e6e01a2b052b3bd4caf539b
BlindedElement = 02d4e6186c9ffa92565055f43f27bb1e2c4103c3325bba0b499
adb99a157987d20fb374096814e438a6b483efa8f2a3307,033a3b052416a8a6d842
a0baea6f5fab99d36645a70c89897a536970d34038eca35afac24906294cb7925b1b
05e4327c8f
EvaluationElement = 037bf8e28a0607b1f8aa59363380b5a7450b66b98017cf03
3797f6c6c74e7625a445f71ace1bea7836ea5baa75d54eb5bd,03b793a9cb2d76991
f1d6cd822abfbfa89fdfa1a06ef42b0bc8ade161e1996ed08c288a08366d4140c762
7bba4e3472bcf
Proof = 27240901b6855d2b58ce84afefa91dd11819d7d5df73f94865a9d7e19020
41200eb732b60b57fa0daf6e456402bb1ccb1aed901af35d3d790cd7c618604b766b
b9271010354da9e4e5507e0468adf177977143db2ddb94d9b70e837ad7578275
ProofRandomScalar = 1b538ff23749be19e92df82df1acd3f606cc9faa9dc7ab25
1997738a3a232f352c2059c25684e6ccea420f8d0c793fa0
Output = 8a0b4829bc8422b1a2301d5471256892883c5e3fe27b998d1010225a706
545637336a20a76f842d8a22e591d382c77e4,8c52d40c1f6cc80208bd610178a503
4d6c4a05584e19b69617f846b09a8545443c63c8aa4d85bf0aad368e0591b1216a
~~~
## OPRF(P-521, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 00a2f8572ee764d2ec34363fb62ef9e8ff48883b5357b6802f43fffe5c5fd
0d11f766bf7086aab33e2dce02cc71d77250ef6ed360a3fd56244abb6bdbc3aa6534
da1
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 01b983705fcc9a39607288b935b0797ac6b3c4b2e848823ac9ae16b3a3b5
816be03432370deb7c3c17d9fc7cb4e0ce646e04e42d638e0fa7a434ed340772a8b5
d626
BlindedElement = 03006ce4a27e778a624d943cf4db48f9d393d3d4dd9cd44b78a
cf2d5b668a12f0ca587962de8c82b5aaa1f0166eb60d511f060aaab895fc6c519332
77bc945add6d74a
EvaluationElement = 030055f7cd3ee3b1734e73ad8bbd4baca72ae8d051160c27
7ee329f23fa2365f9f138b38e6e2c59cc287242eeca01fae83d0c7cc3bb19724ac59
8a188816e7cfe1ca88
Output = aa59060a41ec8ca7b6c47f9c5a31883a44ffd95869a09dbe845ea8ce20c
b290dba0b57c505824a0dcf6f961a2baeb8e6b49df8c158761a3fdb46f39e8e7fcb8
b
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01a03b1096b0316bc8567c89bd70267d35c8ddcb2be2cdc867089a2eb5cf
471b1e6eb4b043b9644c8539857abe3a2022e9c9fd6a1695bbabe8add48bcd149ff3
b841
BlindedElement = 0201459ba64ad0e0f9f689f0ad5ab29ca5b960f5c9da3aef412
6d2d547b871e754b17971fd45e0d64bdcfc8d256c342a141f04e2640705c38936c8c
f53c22ea6b13966
EvaluationElement = 030094036457e8e5bf77719b11f01dd4aa2959efdb3329c3
e3b25493efc3ab572c2e7db104cd5922645320ef51bbb282f84e5f6b08e9b49354f9
d6a9f3a4327a1de6e4
Output = 5efe6f00f45ec4e87e4c9b89aeaec61313c15c0a0a21ee2e41362d6af54
536adf2f68d23c729b92b6fa8d5611764b0272be6cc153d47a0256c8cb44bd740037
a
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 0064799c2f9c0f9e6b9ac2aca5c42687cf15742fb73e086c4954aa0bdc8b8
25911ff03712e8d308c0a6ff5435375036f189391234bf21aac57fa73df155d70da4
7bd
pkSm = 03013e587a7750213bb7c2b338a4507635f1ba60ece346de32ad975373e56
fbabd878f9956996aac83a550ed5f5ba98fcc56817f6230cc7e84cb7eb2a1e1db51d
bfc1b
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 00bbb82117c88bbd91b8954e16c0b9ceed3ce992b198be1ebfba9ba970db
d75beefbfc6d056b7f7ba1ef79f4facbf2d912c26ce2ecc5bb8d66419b379952e96b
d6f5
BlindedElement = 02002ff3ef3f2411aa0358936f852be710af790c9affbced8c3
9b018fd97de0a45d80c66cbf0dbda690ee4f594e0795627e6c6f37a500f223c30f31
c24e73501532e7c
EvaluationElement = 0300769fd56c5174c4e3922900fcefdd5a89c9592f4d8e8f
2396678fa72c01d4f8551ec92d4b5287ca673dc29d8db9bb05d2396121a6b8732b68
ebf310fc2620059d67
Proof = 011fd92f54f6a955a333648d843807bd88f644d235a7d592189da42d721e
a6f7b55ec813146f35982487910aa15bbf5ce90653edb6a1b48c0bfd15758e9358aa
731601baa67a3a59db301f41caa020986ae9e93a80d6c06d92e8c5eef6056fa6f342
6b6054d118dc9fecb77fdcb4fc86b9857ada6de18394ff7d6c574cbd08d746b9dde0
ProofRandomScalar = 00ce4f0d824939827888f4c28773466f3c0a05741260040b
c9f302a4fea13f1d8f2f6b92a02a32d5eb06f81de7960470f06169bee12cf47965b7
2a59946ca3879670
Output = a647c5a940aa19d767ab0e163d1357ca068206b2b78f9e8e1021c0bb0f3
27d20cb8fadf996199d86d4cc0a08ac314493319979e1c2a98a96085b8fabff9f0d0
7
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 009055c99bf9591cb0eab2a72d044c05ca2cc2ef9b609a38546f74b6d688
f70cf205f782fa11a0d61b2f5a8a2a1143368327f3077c68a1545e9aafbba6a90dc0
d40a
BlindedElement = 0301e2ecf7313820e9d47763e12633ce6acf9b3dec89928c83b
de1ede2180dc73553af1317408846af5c53ebfed00d19a4125f4ffb7df9f4260ccc0
84a6f7482414a9d
EvaluationElement = 02000e69591ab605652cb3310e774edf79417e102cf89005
c2c7f2bd3a06060d740817802f2cf484748d93df5b281a4bd835617a97ec9809519d
474ca53bba15cdf014
Proof = 0076fa4275414acb9f87dc9e4f20971d51fcd0d38a980854ac2ad1bd5737
eec23bfb4599d021881f7b3872d2e90d9b47e4219f490cf7f0235b2f0859cb2ef15d
dfd401acb6b0844edf066a5767b4b85536bfee69bdf472acf7a59254cf6578f9f35e
ba51bb58c6428d6b7c9e5c9af97edc66d98886fda9544048bf9ceea6fc745bf970da
ProofRandomScalar = 00b5dfc19eb96faba6382ec845097904db87240b9dd47b1e
487ec625f11a7ba2cc3de74c5078a81806f74dd65065273c5bd886c7f87ff8c5f39f
90320718eff747e3
Output = 8d109503ccced41cbec087dab86c607763020be93bdd5ec8508cb078607
1a2b22a7b06150242bcaf6ea1b555a994e0266647eb72914caf73cabe53ddfb0f940
d
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01c6cf092d80c7cf2cb55388d899515238094c800bdd9c65f71780ba85f5
ae9b4703e17e559ca3ccd1944f9a70536c175f11a827452672b60d4e9f89eba28104
6e29,00cba1ba1a337759061965a423d9d3d6e1e1006dc8984ad28a4c93ecfc36fc2
171046b3c4284855cfa2434ed98db9e68a597db2c14728fade716a6a82d600444b26
e
BlindedElement = 0201e22c01df5ac0502842fad603f7a1e1183bcc79a5cb04bb7
befdea870a9a6ea96fbccd752ea9927a9e1e28438098f693461e81832a3f690616bf
983fced079f3a33,0300b49216dd8ba5ba1275d8345679f70fbc6baf4f4b32a03e91
7165a18afa9fad849c48eecb4bae965057ef7c215b52b42ca53c8d5f650633e0bb70
97f2bd809d09ea
EvaluationElement = 03002949c2478249b918a0cf2cd870226541a81d2f3e88c4
7119f732301e749c3dea317c11174a18b89d1b9d2aa4f6ae92ae724e03a4800a26b7
c827b00199f1114bcd,0300924ab017ea6e6328a0b0f341bbeb7d209c67ac169fa4e
f7b04055c66b92aa9657f5d83b0b1ee9c79f3f0198519c97fef07dbecf3f6d477755
0242a1c87953f9461
Proof = 01f5d3c3f835d91aa88202f0fe8728180eeffe7fbc66ffe3f7a7dd958696
a7cd3d47b3c0ec6cd59e9ee23090137293e6f42269923f3d4a1659bc706fd9762070
7d230028cd4b0aa237b91a352fce81248936826ba99e7bd5103a871715126014b8d4
7447e5f20192ed377a7431516fbd82763098ba23f9d15b84fe24fb1126beb0d46f03
ProofRandomScalar = 00d47b0d4ca4c64825ba085de242042b84d9ebe3b2e9de07
678ff96713dfe16f40f2c662a56ed2db95e1e7bf2dea02bd1fa76e953a630772f68b
53baade9962d1646
Output = a647c5a940aa19d767ab0e163d1357ca068206b2b78f9e8e1021c0bb0f3
27d20cb8fadf996199d86d4cc0a08ac314493319979e1c2a98a96085b8fabff9f0d0
7,8d109503ccced41cbec087dab86c607763020be93bdd5ec8508cb0786071a2b22a
7b06150242bcaf6ea1b555a994e0266647eb72914caf73cabe53ddfb0f940d
~~~
"#;
-180
View File
@@ -1,180 +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.
//! Helper functions
use crate::errors::InternalError;
use core::array::IntoIter;
use generic_array::{typenum::U0, ArrayLength, GenericArray};
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp<L: ArrayLength<u8>>(
input: usize,
) -> Result<GenericArray<u8, L>, InternalError> {
const SIZEOF_USIZE: usize = core::mem::size_of::<usize>();
// Check if input >= 256^length
if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 {
return Err(InternalError::SerializationError);
}
if L::USIZE <= SIZEOF_USIZE {
return Ok(GenericArray::clone_from_slice(
&input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..],
));
}
let mut output = GenericArray::default();
output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes());
Ok(output)
}
/// Simplifies handling of [`serialize()`] output and implements [`Iterator`].
pub(crate) struct Serialized<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> {
octet: GenericArray<u8, L1>,
input: Input<'a, L2>,
}
enum Input<'a, L: ArrayLength<u8>> {
Owned(GenericArray<u8, L>),
Borrowed(&'a [u8]),
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> IntoIterator for &'a Serialized<'a, L1, L2> {
type Item = &'a [u8];
type IntoIter = IntoIter<&'a [u8], 2>;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new([
&self.octet,
match self.input {
Input::Owned(ref bytes) => bytes,
Input::Borrowed(bytes) => bytes,
},
])
}
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize<L: ArrayLength<u8>>(
input: &[u8],
) -> Result<Serialized<L, U0>, InternalError> {
Ok(Serialized {
octet: i2osp::<L>(input.len())?,
input: Input::Borrowed(input),
})
}
// Variation of `serialize` that takes an owned `input`
pub(crate) fn serialize_owned<L1: ArrayLength<u8>, L2: ArrayLength<u8>>(
input: GenericArray<u8, L2>,
) -> Result<Serialized<'static, L1, L2>, InternalError> {
Ok(Serialized {
octet: i2osp::<L1>(input.len())?,
input: Input::Owned(input),
})
}
macro_rules! chain_name {
($var:ident, $mod:ident) => {
$mod
};
($var:ident) => {
$var
};
}
macro_rules! chain_skip {
($var:ident, $feed:expr) => {
$feed
};
($var:ident) => {
&$var
};
}
/// The purpose of this macro is to simplify [`concat`](alloc::slice::Concat::concat)ing
/// slices into an [`Iterator`] to avoid allocation
macro_rules! chain {
(
$var:ident,
$item1:expr $(=> |$mod1:ident| $feed1:expr)?,
$($item2:expr $(=> |$mod2:ident| $feed2:expr)?),+$(,)?
) => {
let chain_name!(__temp$(, $mod1)?) = $item1;
let $var = (chain_skip!(__temp$(, $feed1)?)).into_iter();
$(
let chain_name!(__temp$(, $mod2)?) = $item2;
let $var = $var.chain(chain_skip!(__temp$(, $feed2)?));
)+
};
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::{U1, U2};
use proptest::{collection::vec, prelude::*};
use sha2::Sha512;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp::<U1>(0).is_ok());
assert!(i2osp::<U1>(255).is_ok());
assert!(i2osp::<U1>(256).is_err());
assert!(i2osp::<U1>(257).is_err());
assert!(i2osp::<U2>(256 * 256 - 1).is_ok());
assert!(i2osp::<U2>(256 * 256).is_err());
assert!(i2osp::<U2>(256 * 256 + 1).is_err());
}
proptest! {
#[test]
fn test_nocrash_nonverifiable_client(bytes in vec(any::<u8>(), 0..200)) {
NonVerifiableClient::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_verifiable_client(bytes in vec(any::<u8>(), 0..200)) {
VerifiableClient::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_nonverifiable_server(bytes in vec(any::<u8>(), 0..200)) {
NonVerifiableServer::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_verifiable_server(bytes in vec(any::<u8>(), 0..200)) {
VerifiableServer::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_blinded_element(bytes in vec(any::<u8>(), 0..200)) {
BlindedElement::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_evaluation_element(bytes in vec(any::<u8>(), 0..200)) {
EvaluationElement::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_proof(bytes in vec(any::<u8>(), 0..200)) {
Proof::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
}
}
+620 -958
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