Compare commits

..
72 Commits
Author SHA1 Message Date
breakingbread b702b6164e chore: bump to 0.6.0-rc.0, migrate to elliptic-curve 0.14, hash2curve 0.14, rand_core 0.10 2026-06-27 14:38:19 +02:00
breakingbread d42e7948a1 Delete directory '.github' 2026-06-27 14:38:13 +02:00
Kevin LewiandGitHub 0a7dc184ca Publishing v0.6.0-pre.1 (#156) 2026-04-06 13:33:53 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f5b7e689e7 Bump actions/cache from 4 to 5 (#148)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 14:46:54 -08:00
raphaelrobertandGitHub a22d46fd96 chore: update more dependencies (#145)
* update more dependencies

* cargo fmt

* address review comments

* fix format
2026-01-25 14:37:55 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f23cdfab2d Bump actions/checkout from 4 to 6 (#149)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 14:54:37 -08:00
Kevin LewiandGitHub 256ab7bc52 Fixing docs issue (#147) 2025-11-10 01:32:43 -08:00
Kevin LewiandGitHub eb55e9f5b5 Publishing v0.6.0-pre.0 (#146) 2025-11-08 13:46:14 -08:00
raphaelrobertandGitHub e944f9db3b chore: update generic-array to v1 (#143)
* update generic-array to v1

* revert displaydoc removal

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

* Bump MSRV to v1.83

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

---------

Signed-off-by: daxpedda <[email protected]>
2025-04-15 13:30:56 -07:00
Kevin LewiandGitHub f0531f0812 Publishing v0.5 (#133) 2024-03-06 17:49:35 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c93884aca3 Bump actions/cache from 3 to 4 (#132)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

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

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

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

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

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

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

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

* Improve documentation
2022-12-17 18:12:23 -08:00
daxpeddaandGitHub 6913b5deaa Fix Clippy (#85) 2022-12-10 14:21:04 -08:00
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
Kevin LewiandGitHub d9dc5d0a13 Updating test vectors to sync with draft version 08 (#32) 2021-10-25 22:06:42 -07:00
35 changed files with 5310 additions and 4093 deletions
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
-125
View File
@@ -1,125 +0,0 @@
name: Rust CI
on:
push:
branches:
- main
pull_request:
types: [opened, repoened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- ristretto255_u64
- ristretto255_u32
- p256,ristretto255_u64
frontend_feature:
- serde
- danger
toolchain:
- stable
- 1.51.0
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ 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 }}
build-no-std:
name: Build with no-std on ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
# for wasm
- wasm32-unknown-unknown
# for any no_std target
- thumbv6m-none-eabi
backend_feature:
-
- --features ristretto255_u64
- --features ristretto255_u32
- --features p256
frontend_feature:
-
- --features serde
- --features danger
steps:
- uses: actions/checkout@v2
- 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 }}
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
env:
RUSTDOCFLAGS: -D warnings
with:
command: doc
args: --no-deps --document-private-items --features std,p256
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
-28
View File
@@ -1,28 +0,0 @@
name: Publish
on:
release:
types: [published]
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
- name: Login to crates.io
run: cargo login $CRATES_IO_TOKEN
env:
CRATES_IO_TOKEN: ${{ secrets.crates_io_token }}
- name: Dry run publish voprf
run: cargo publish --dry-run --manifest-path Cargo.toml
- name: Publish voprf
run: cargo publish --manifest-path Cargo.toml
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
+74
View File
@@ -1,5 +1,79 @@
# Changelog
## 0.6.0-rc.0 (June 27, 2026)
* MSRV bumped to 1.87
* Migrated from `elliptic-curve 0.13` to `0.14`
* Replaced `generic-array` with `hybrid-array`
* Updated `digest` dependency to 0.11
* Updated `rand_core` dependency to 0.10
* Updated `rand` dependency to 0.10
* Updated `sha2` dependency to 0.11
* Updated `p256`, `p384`, `p521` dependencies to 0.14.0-rc
* Replaced `elliptic-curve/hash2curve` feature with standalone `hash2curve 0.14` crate
* Updated `hash_to_scalar` to use `MapToCurve::Length` as OKM length per RFC 9380, replacing the removed `GroupDigest::hash_to_scalar` method
* Updated `random_scalar` to consume exactly `ScalarLen` bytes per attempt, adapting to the new `rand_core 0.10` API
* Added `OkmLen` associated type to `Group` trait
## 0.6.0-pre.1 (April 6, 2026)
* MSRV bumped to 1.85
* Updated rand_core dependency to 0.9
* Updated rand dependency to 0.9
* Updated subtle dependency to 2.6
* Fixed docs issue
## 0.6.0-pre.0 (November 8, 2025)
* MSRV bumped to 1.83
* Updated Ristretto255 random scalar generation
* Updated generic-array to v1
## 0.5.0 (March 6, 2024)
* Just a version bump from v0.5.0-pre.7
## 0.5.0-pre.7 (January 11, 2024)
* Updated to be in sync with RFC 9497
## 0.5.0-pre.6 (July 24, 2023)
* Updated curve25519-dalek dependency to 4
## 0.5.0-pre.5 (June 27, 2023)
* Updated curve25519-dalek dependency to 4.0.0-rc.3
## 0.5.0-pre.4 (May 20, 2023)
* Updated curve25519-dalek dependency to 4.0.0-rc.2
## 0.5.0-pre.3 (March 4, 2023)
* Updated to be in sync with draft-irtf-cfrg-voprf-19
* Increased MSRV to 1.65
* Updated p256 dependency to v0.13
* Added p384 tests
## 0.5.0-pre.2 (February 3, 2023)
* Increased MSRV to 1.60
* Updated p256 dependency to v0.12
* Updated curve25519-dalek dependency to 4.0.0-rc.1
## 0.5.0-pre.1 (December 19, 2022)
* Updated curve25519-dalek dependency to 4.0.0-pre.5
## 0.4.0 (September 15, 2022)
* Updated to be in sync with draft-irtf-cfrg-voprf-11, with
the addition of the POPRF mode
* 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
## 0.2.0 (October 18, 2021)
* Removed the CipherSuite interface
-76
View File
@@ -1,76 +0,0 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <opensource-conduct@fb.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+2 -20
View File
@@ -2,29 +2,11 @@
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to voprf, you agree that your contributions will be
licensed under the LICENSE file in the root directory of this source tree.
licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
directory of this source tree.
+45 -36
View File
@@ -1,55 +1,64 @@
[package]
name = "voprf"
version = "0.2.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.87"
version = "0.6.0-rc.0"
[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"]
ristretto255-ciphersuite = ["ristretto255", "dep:sha2"]
serde = ["curve25519-dalek?/serde", "hybrid-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", default-features = false, features = ["rand_core", "zeroize"], optional = true }
derive-where = { version = "1", features = ["zeroize-on-drop"] }
digest = "0.11"
displaydoc = { version = "0.2", default-features = false }
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 }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", default-features = false }
elliptic-curve = { version = "0.14", features = [
"sec1",
] }
hash2curve = "0.14"
hybrid-array = "0.4"
rand_core = { version = "0.10", default-features = false, features = [] }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.11", default-features = false, optional = true }
subtle = { version = "2.6", 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.14.0-rc", default-features = false, features = [
"hash2curve",
"oprf",
] }
p384 = { version = "0.14.0-rc", default-features = false, features = [
"hash2curve",
"oprf",
] }
p521 = { version = "0.14.0-rc", default-features = false, features = [
"hash2curve",
"oprf",
] }
proptest = "1"
rand = "0.8"
rand = "0.10"
regex = "1"
sha2 = "0.9"
voprf = { path = "", default-features = false, features = ["std", "danger"] }
serde_json = "1"
sha2 = "0.11"
[package.metadata.docs.rs]
features = ["danger", "p256", "std"]
targets = []
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = []
-12
View File
@@ -1,12 +0,0 @@
## License
Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
+6 -4
View File
@@ -3,7 +3,7 @@ An implementation of a (verifiable) oblivious pseudorandom function (VOPRF)
A VOPRF is a verifiable oblivious pseudorandom function, a protocol between a client and a server. The regular (non-verifiable) OPRF is also supported in this implementation.
This implementation is based on the [Internet Draft for VOPRF](https://github.com/cfrg/draft-irtf-cfrg-voprf).
This implementation is based on [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497).
Documentation
-------------
@@ -16,12 +16,12 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.2"
voprf = { package = "voprf-vexahub", version = "0.6.0-rc.0" }
```
### Minimum Supported Rust Version
Rust **1.51** or higher.
Rust **1.87** or higher.
Contributors
------------
@@ -32,4 +32,6 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
License
-------
This project is [licensed](./LICENSE) under either Apache 2.0 or MIT, at your option.
This project is dual-licensed under either the [MIT license](./LICENSE-MIT)
or the [Apache License, Version 2.0](./LICENSE-APACHE).
You may select, at your option, one of the above-listed licenses.
+1
View File
@@ -0,0 +1 @@
newline_style = "Unix"
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
use crate::Group;
use core::ops::Mul;
use digest::block_api::BlockSizeUser;
use digest::typenum::{IsLess, IsLessOrEqual, U256};
use digest::{Digest, FixedOutput, HashMarker, OutputSizeUser};
use hybrid_array::typenum::{IsGreaterOrEqual, Prod, True, U2};
use hybrid_array::ArraySize;
/// Configures the underlying primitives used in VOPRF
pub trait CipherSuite
where
<Self::Group as Group>::SecurityLevel: Mul<U2>,
<Self::Hash as OutputSizeUser>::OutputSize: ArraySize
+ IsLess<U256>
+ IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize, Output = True>
+ IsGreaterOrEqual<Prod<<Self::Group as Group>::SecurityLevel, U2>, Output = True>,
{
/// The ciphersuite identifier as dictated by
/// <https://www.rfc-editor.org/rfc/rfc9497>
const ID: &'static [u8];
/// 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: Digest + BlockSizeUser + Default + FixedOutput + HashMarker;
}
+520
View File
@@ -0,0 +1,520 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Common functionality between multiple OPRF modes.
use core::convert::TryFrom;
use core::ops::Add;
use derive_where::derive_where;
use digest::{Digest, Output, OutputSizeUser};
use hybrid_array::typenum::{IsLess, Unsigned, U2, U256, U9};
use hybrid_array::{Array, ArrayN, ArraySize};
use rand_core::{TryCryptoRng, TryRng};
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: ArrayN<u8, 5> = Array(*b"Seed-");
pub(crate) const STR_DERIVE_KEYPAIR: ArrayN<u8, 13> = Array(*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: ArrayN<u8, 13> = Array(*b"HashToScalar-");
pub(crate) const STR_HASH_TO_GROUP: ArrayN<u8, 12> = Array(*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,
);
/// 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,
);
/// 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>);
/// 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> {
#[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: TryRng + TryCryptoRng>(
rng: &mut R,
k: <CS::Group as Group>::Scalar,
a: <CS::Group as Group>::Elem,
b: <CS::Group as Group>::Elem,
cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
mode: Mode,
) -> Result<Proof<CS>> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
let r = CS::Group::random_scalar(rng)?;
let 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 ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
proof: &Proof<CS>,
mode: Mode,
) -> Result<()> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.2
let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?;
let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar);
let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar);
// 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>> {
// https://www.rfc-editor.org/rfc/rfc9497#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::<<CS::Hash as OutputSizeUser>::OutputSize>();
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> {
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> {
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> {
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> {
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> {
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: Array<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
) -> Result<Output<CS::Hash>> {
// 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_slice().len()).map_err(|_| Error::Input)?)
.chain_update(issued_element)
.chain_update(STR_FINALIZE)
.finalize())
}
pub(crate) struct Dst<L: ArraySize> {
dst_1: Array<u8, L>,
dst_2: &'static [u8],
}
impl<L: ArraySize> Dst<L> {
pub(crate) fn new<CS, TL>(par_1: Array<u8, TL>, mode: Mode) -> Self
where
CS: CipherSuite,
TL: ArraySize + Add<U9, Output = L>,
{
// Generates the contextString parameter as defined in
// <https://www.rfc-editor.org/rfc/rfc9497#section-3.1>
let par_2 = ArrayN::<u8, 7>::from(STR_OPRF)
.concat(ArrayN::<u8, 1>::from([mode.to_u8()]))
.concat(ArrayN::<u8, 1>::from([b'-']));
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]
}
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: ArraySize + IsLess<U256>>() -> Array<u8, U2> {
L::U16.to_be_bytes().into()
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Errors which are produced during an execution of the protocol
/// [`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, displaydoc::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,
/// Random number generator failure.
Rng,
}
/// 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,
}
impl core::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 {}
+140
View File
@@ -0,0 +1,140 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use core::ops::{Add, Mul};
use digest::block_api::BlockSizeUser;
use digest::typenum::{IsLess, IsLessOrEqual, U256};
use digest::{FixedOutput, HashMarker};
use elliptic_curve::group::cofactor::CofactorGroup;
use elliptic_curve::sec1::{FromSec1Point, ModulusSize, ToSec1Point};
use elliptic_curve::{
AffinePoint, Field, FieldBytes, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar,
SecretKey,
};
use hash2curve::{hash_to_scalar, ExpandMsgXmd, GroupDigest};
use hybrid_array::typenum::{IsGreaterOrEqual, Prod, Sum, True, U2};
use hybrid_array::{Array, ArraySize};
use rand_core::TryCryptoRng;
use super::Group;
use crate::{CipherSuite, Error, InternalError, Result};
type ElemLen<C> = <ScalarLen<C> as ModulusSize>::CompressedPointSize;
type ScalarLen<C> = FieldBytesSize<C>;
impl<C> Group for C
where
C: GroupDigest + CipherSuite + hash2curve::MapToCurve,
C::SecurityLevel: Mul<U2>,
C::SecurityLevel: ArraySize,
<C::SecurityLevel as Mul<U2>>::Output: ArraySize,
ProjectivePoint<Self>: CofactorGroup + ToSec1Point<Self>,
ScalarLen<Self>: ModulusSize,
ScalarLen<Self>: ArraySize,
ScalarLen<Self>: hybrid_array::typenum::NonZero,
Scalar<Self>: elliptic_curve::ops::Reduce<Array<u8, ScalarLen<Self>>>,
Scalar<Self>: elliptic_curve::ops::Reduce<Array<u8, <C as hash2curve::MapToCurve>::Length>>,
AffinePoint<Self>: FromSec1Point<Self> + ToSec1Point<Self>,
// `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen`
ScalarLen<Self>: Add<ElemLen<Self>>,
Sum<ScalarLen<Self>, ElemLen<Self>>: ArraySize,
// `ProofLen`
ScalarLen<Self>: Add<ScalarLen<Self>>,
Sum<ScalarLen<Self>, ScalarLen<Self>>: ArraySize,
ElemLen<Self>: ArraySize,
{
type Elem = ProjectivePoint<Self>;
type ElemLen = ElemLen<Self>;
type Scalar = Scalar<Self>;
type ScalarLen = ScalarLen<Self>;
type SecurityLevel = C::SecurityLevel;
type OkmLen = <C as hash2curve::MapToCurve>::Length;
// Implements the `hash_to_curve()` function from
// https://www.rfc-editor.org/rfc/rfc9380.html#section-3
fn hash_to_curve<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Elem, InternalError> {
Self::hash_from_bytes(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, Output = True>,
C::SecurityLevel: Mul<U2>,
H::OutputSize: IsGreaterOrEqual<Prod<C::SecurityLevel, U2>, Output = True>,
{
hash_to_scalar::<C, ExpandMsgXmd<H>, <C as hash2curve::MapToCurve>::Length>(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) -> Array<u8, Self::ElemLen> {
let bytes = elem.to_sec1_point(true);
let bytes = bytes.as_bytes();
let mut result = Array::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: TryCryptoRng>(rng: &mut R) -> Result<Self::Scalar> {
loop {
let mut bytes = FieldBytes::<Self>::default();
rng.try_fill_bytes(&mut bytes).map_err(|_| Error::Rng)?;
if let Ok(key) = SecretKey::<Self>::from_slice(&bytes) {
return Ok(*key.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) -> Array<u8, Self::ScalarLen> {
let bytes: FieldBytes<Self> = scalar.into();
let mut result = Array::<u8, Self::ScalarLen>::default();
result.as_mut_slice().copy_from_slice(bytes.as_ref());
result
}
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
SecretKey::<Self>::from_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));
}
}
}
+115 -124
View File
@@ -1,155 +1,146 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Defines the Group trait to specify the underlying prime order group
#[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 rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use digest::block_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker};
use hybrid_array::typenum::{IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, Sum, True, U2, U256};
use hybrid_array::{Array, ArraySize};
use rand_core::{TryCryptoRng, TryRng};
#[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>
/// subgroup is noted additively — as in the RFC — in this trait.
pub trait Group
where
// `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen`
Self::ScalarLen: Add<Self::ElemLen>,
Sum<Self::ScalarLen, Self::ElemLen>: ArraySize,
// `ProofLen`
Self::ScalarLen: Add<Self::ScalarLen>,
Sum<Self::ScalarLen, Self::ScalarLen>: ArraySize,
{
/// 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
/// 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;
type ElemLen: ArraySize + '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: ArraySize + 'static;
if Self::ct_eq(&elem, &<Self as Group>::identity()).into() {
// found the identity element
return Err(InternalError::PointError);
}
/// Security parameter `k` in bytes (i.e. `k / 8`), as defined in
/// [RFC 9380 §8](https://www.rfc-editor.org/rfc/rfc9380#section-8).
///
/// Used to enforce `H::OutputSize >= 2 * SecurityLevel` in
/// `hash_to_curve` and `hash_to_scalar`, which corresponds to the
/// `expand_message` requirement `len_in_bytes = 2 * k / 8`.
type SecurityLevel: ArraySize;
Ok(elem)
/// The OKM length for hash_to_scalar (>= ScalarLen, used by hash_to_field).
type OkmLen: ArraySize + hybrid_array::typenum::NonZero;
/// 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, Output = True>,
Self::SecurityLevel: Mul<U2>,
H::OutputSize: IsGreaterOrEqual<Prod<Self::SecurityLevel, U2>, Output = True>;
/// 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, Output = True>,
Self::SecurityLevel: Mul<U2>,
H::OutputSize: IsGreaterOrEqual<Prod<Self::SecurityLevel, U2>, Output = True>;
/// 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) -> Array<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.
///
/// # Errors
/// [`Error::Rng`](crate::Error::Rng) if the random number generator fails.
fn random_scalar<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<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) -> Array<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)]
-581
View File
@@ -1,581 +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 from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3
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()));
}
}
}
+133 -109
View File
@@ -1,134 +1,158 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use core::num::NonZeroU16;
use core::ops::Mul;
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::block_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker};
use hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
use hybrid_array::typenum::{
IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, True, U16, U2, U256, U32, U64,
};
use hybrid_array::Array;
use rand_core::{TryCryptoRng, TryRng};
use subtle::ConstantTimeEq;
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 rand_core::{CryptoRng, RngCore};
use crate::{Error, InternalError, Result};
/// 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;
/// [`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 [u8] = b"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;
type SecurityLevel = U16;
type OkmLen = U64;
// 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>
// https://www.rfc-editor.org/rfc/rfc9380.html#appendix-B
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, Output = True>,
Self::SecurityLevel: Mul<U2>,
H::OutputSize: IsGreaterOrEqual<Prod<Self::SecurityLevel, U2>, Output = True>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(Some(msg), dst)?;
let mut uniform_bytes = [0u8; 64];
Ok(RistrettoPoint::from_uniform_bytes(
uniform_bytes
.as_slice()
.try_into()
.map_err(|_| InternalError::HashToCurveError)?,
))
<ExpandMsgXmd<H> as ExpandMsg<U16>>::expand_message(
input,
dst,
NonZeroU16::new(64).unwrap(),
)
.map_err(|_| InternalError::Input)?
.fill_bytes(&mut uniform_bytes)
.map_err(|_| InternalError::Input)?;
Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes))
}
// 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>
// https://www.rfc-editor.org/rfc/rfc9497#section-4.1
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, Output = True>,
Self::SecurityLevel: Mul<U2>,
H::OutputSize: IsGreaterOrEqual<Prod<Self::SecurityLevel, U2>, Output = True>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(input, dst)?;
let mut uniform_bytes = [0u8; 64];
Ok(Scalar::from_bytes_mod_order_wide(
uniform_bytes
.as_slice()
.try_into()
.map_err(|_| InternalError::HashToCurveError)?,
))
<ExpandMsgXmd<H> as ExpandMsg<U16>>::expand_message(
input,
dst,
NonZeroU16::new(64).unwrap(),
)
.map_err(|_| InternalError::Input)?
.fill_bytes(&mut uniform_bytes)
.map_err(|_| InternalError::Input)?;
Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes))
}
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 random_nonzero_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)
};
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 {
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 base_point() -> Self {
fn base_elem() -> Self::Elem {
RISTRETTO_BASEPOINT_POINT
}
fn identity() -> Self {
<Self as Identity>::identity()
fn identity_elem() -> Self::Elem {
RistrettoPoint::identity()
}
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
// serialization of a group element
fn serialize_elem(elem: Self::Elem) -> Array<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: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self::Scalar> {
loop {
let mut scalar_bytes = [0u8; 32];
rng.try_fill_bytes(&mut scalar_bytes)
.map_err(|_| Error::Rng)?;
if let Ok(scalar) = Self::deserialize_scalar(&scalar_bytes) {
break Ok(scalar);
}
}
}
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
scalar.invert()
}
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
scalar.ct_eq(&Scalar::ZERO)
}
#[cfg(test)]
fn zero_scalar() -> Self::Scalar {
Scalar::ZERO
}
fn serialize_scalar(scalar: Self::Scalar) -> Array<u8, Self::ScalarLen> {
scalar.to_bytes().into()
}
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)
}
}
+33 -25
View File
@@ -1,50 +1,58 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Includes a series of tests for the group implementations
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;
use p521::NistP521;
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>()?;
test_identity_element_error::<NistP521>()?;
test_zero_scalar_error::<NistP521>()?;
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)
}
}
};
}
+422 -267
View File
@@ -1,275 +1,354 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
//!
//! Note: This implementation is in sync with
//! [draft-irtf-cfrg-voprf-07](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html),
//! but this specification is subject to change, until the final version
//! published by the IETF.
//! [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497).
//!
//! # Overview
//!
//! 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::SysRng;
//! use rand::Rng;
//! use voprf::OprfServer;
//!
//! let mut server_rng = OsRng;
//! let server = NonVerifiableServer::<Group, Hash>::new(&mut server_rng)
//! .expect("Unable to construct server");
//! let mut server_rng = SysRng;
//! 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::SysRng;
//! use rand::Rng;
//! 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 mut client_rng = SysRng;
//! 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # let mut client_rng = SysRng;
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # 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");
//! # use voprf::OprfServer;
//! # let mut server_rng = SysRng;
//! # 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::OprfClient;
//! # use rand::{rngs::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # let mut client_rng = SysRng;
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # 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");
//! # use voprf::OprfServer;
//! # let mut server_rng = SysRng;
//! # 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::SysRng, Rng};
//! #
//! # let mut client_rng = SysRng;
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::OprfServer;
//! # let mut server_rng = SysRng;
//! # 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::SysRng;
//! use rand::Rng;
//! use voprf::VoprfServer;
//!
//! let mut server_rng = OsRng;
//! let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! .expect("Unable to construct server");
//! let mut server_rng = SysRng;
//! 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::SysRng;
//! use rand::Rng;
//! 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 mut client_rng = SysRng;
//! 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerEvaluateResult, VoprfClient};
//! # use rand::{rngs::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # let mut client_rng = SysRng;
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # 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");
//! # use voprf::VoprfServer;
//! # let mut server_rng = SysRng;
//! # 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # let mut client_rng = SysRng;
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # 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(
//! # use voprf::VoprfServer;
//! # let mut server_rng = SysRng;
//! # 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,
//! # &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(),
//! None,
//! ).expect("Unable to perform client finalization");
//! )
//! .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::SysRng, Rng};
//! #
//! # let mut client_rng = SysRng;
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VoprfServer;
//! # let mut server_rng = SysRng;
//! # 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 +357,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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::VoprfClient;
//! # use rand::{rngs::SysRng, Rng};
//! #
//! let mut client_rng = OsRng;
//! let mut client_rng = SysRng;
//! 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[cfg(feature = "ristretto255")]
//! # type CipherSuite = voprf::Ristretto255;
//! # #[cfg(not(feature = "ristretto255"))]
//! # type CipherSuite = p256::NistP256;
//! # use voprf::{VoprfServerBatchEvaluateFinishResult, VoprfClient};
//! # use rand::{rngs::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_rng = SysRng;
//! # 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;
//! 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");
//! # use voprf::VoprfServer;
//! let mut server_rng = SysRng;
//! # 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;
//! # use rand::{rngs::OsRng, RngCore};
//! # #[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::SysRng, Rng};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_rng = SysRng;
//! # 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;
//! 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(
//! # use voprf::VoprfServer;
//! let mut server_rng = SysRng;
//! # 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::SysRng, Rng};
//! #
//! # let mut client_rng = SysRng;
//! # 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 = SysRng;
//! # 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.rfc-editor.org/rfc/rfc9497#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)]
#![allow(clippy::multiple_crate_versions)]
#![cfg_attr(not(feature = "std"), no_std)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_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(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 +568,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,
};
+419
View File
@@ -0,0 +1,419 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Contains the main OPRF API
use core::iter::{self, Map};
use derive_where::derive_where;
use digest::{Digest, Output};
use hybrid_array::typenum::Unsigned;
use hybrid_array::Array;
use rand_core::{TryCryptoRng, TryRng};
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> {
#[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> {
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
pub(crate) sk: <CS::Group as Group>::Scalar,
}
/////////////////////////
// API Implementations //
// =================== //
/////////////////////////
impl<CS: CipherSuite> OprfClient<CS> {
/// Computes the first step for the multiplicative blinding version of
/// DH-OPRF.
///
/// # Errors
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
pub fn blind<R: TryRng + TryCryptoRng>(
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> {
/// 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: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self> {
let mut seed = Array::<_, <CS::Group as Group>::ScalarLen>::default();
rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
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> {
/// 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<'a, CS, I, IE> {
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::SysRng;
use rand::TryRng;
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> {
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>() {
let input = b"input";
let mut rng = SysRng;
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>() {
let mut rng = SysRng;
let mut input = [0u8; 64];
rng.try_fill_bytes(&mut input).unwrap();
let client_blind_result = OprfClient::<CS>::blind(&input, &mut rng).unwrap();
let client_finalize_result = client_blind_result
.state
.finalize(&input, &EvaluationElement(client_blind_result.message.0))
.unwrap();
let dst = 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>() {
let input = b"input";
let mut rng = SysRng;
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>() {
let input = b"input";
let mut rng = SysRng;
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>() {
let input = b"input";
let mut rng = SysRng;
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;
use p521::NistP521;
#[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>();
base_retrieval::<NistP521>();
base_inversion_unsalted::<NistP521>();
server_evaluate::<NistP521>();
zeroize_oprf_client::<NistP521>();
zeroize_oprf_server::<NistP521>();
Ok(())
}
}
+921
View File
@@ -0,0 +1,921 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! 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::{Digest, Output, OutputSizeUser};
use hybrid_array::typenum::Unsigned;
use hybrid_array::{Array, ArraySize};
use rand_core::{TryCryptoRng, TryRng};
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> {
#[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> {
#[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> {
/// 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: TryRng + TryCryptoRng>(
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>>
where
<<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
{
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,
<<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
{
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> {
/// 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: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self> {
let mut seed = Array::<_, <CS::Group as Group>::ScalarLen>::default();
rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
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: TryRng + TryCryptoRng>(
&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: TryRng + TryCryptoRng, 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: TryRng + TryCryptoRng,
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> {
/// 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> {
/// 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> {
/// 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> {
/// 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> {
/// 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,
);
/// 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> {
/// 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
&'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> {
// 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> {
// 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
&'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)
.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<Output<<CS as CipherSuite>::Hash>>,
>;
/// 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 as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
{
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::ptr;
use rand::rngs::SysRng;
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> {
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>() {
let input = b"input";
let info = b"info";
let mut rng = SysRng;
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>() {
let input = b"input";
let info = b"info";
let mut rng = SysRng;
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>() {
let input = b"input";
let info = Some(b"info".as_slice());
let mut rng = SysRng;
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>() {
let input = b"input";
let mut rng = SysRng;
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>() {
let input = b"input";
let info = b"info";
let mut rng = SysRng;
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;
use p521::NistP521;
#[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>();
verifiable_retrieval::<NistP521>();
verifiable_bad_public_key::<NistP521>();
verifiable_server_evaluate::<NistP521>();
zeroize_verifiable_client::<NistP521>();
zeroize_verifiable_server::<NistP521>();
Ok(())
}
}
+314 -122
View File
@@ -1,192 +1,384 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Handles the serialization of each of the components used
//! in the VOPRF protocol
//! Handles the serialization of each of the components used in the VOPRF
//! protocol
use hybrid_array::typenum::{Sum, Unsigned};
use hybrid_array::Array;
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> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[G::scalar_as_bytes(self.blind).as_slice(), &self.data].concat()
pub fn serialize(&self) -> Array<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> {
/// 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) -> Array<u8, VoprfClientLen<CS>> {
<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> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
G::scalar_as_bytes(self.sk).to_vec()
pub fn serialize(&self) -> Array<u8, PoprfClientLen<CS>> {
<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> {
/// 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) -> Array<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> {
/// 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) -> Array<u8, VoprfServerLen<CS>> {
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> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
pub fn serialize(&self) -> Array<u8, PoprfServerLen<CS>> {
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> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
pub fn serialize(&self) -> Array<u8, ProofLen<CS>> {
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> {
/// Serialization into bytes
pub fn serialize(&self) -> Array<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> {
/// Serialization into bytes
pub fn serialize(&self) -> Array<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<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self>;
}
impl<T> SliceExt for [T] {
fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a 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 hybrid_array::Array;
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>,
{
Array::<_, 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>,
{
Array::<_, 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[..]);
let _ = $item::<p521::NistP521>::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);
}
}
}
+922
View File
@@ -0,0 +1,922 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! The VOPRF test vectors taken from:
//! https://www.rfc-editor.org/rfc/rfc9497#appendix-A
pub(crate) const VECTORS: &str = r#"
A.1. ristretto255-SHA512
A.1.1. OPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 5ebcea5ee37023ccb9fc2d2019f9d7737be85591ae8652ffa9ef0f4d37063
b0e
A.1.1.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = 609a0ae68c15a3cf6903766461307e5c8bb2f95e7e6550e1ffa
2dc99e412803c
EvaluationElement = 7ec6578ae5120958eb2db1745758ff379e77cb64fe77b0b2
d8cc917ea0869c7e
Output = 527759c3d9366f277d8c6020418d96bb393ba2afb20ff90df23fb770826
4e2f3ab9135e3bd69955851de4b1f9fe8a0973396719b7912ba9ee8aa7d0b5e24bcf
6
A.1.1.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = da27ef466870f5f15296299850aa088629945a17d1f5b7f5ff0
43f76b3c06418
EvaluationElement = b4cbf5a4f1eeda5a63ce7b77c7d23f461db3fcab0dd28e4e
17cecb5c90d02c25
Output = f4a74c9c592497375e796aa837e907b1a045d34306a749db9f34221f7e7
50cb4f2a6413a6bf6fa5e19ba6348eb673934a722a7ede2e7621306d18951e7cf2c7
3
A.1.2. VOPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = e6f73f344b79b379f1a0dd37e07ff62e38d9f71345ce62ae3a9bc60b04ccd
909
pkSm = c803e2cc6b05fc15064549b5920659ca4a77b2cca6f04f6b357009335476a
d4e
A.1.2.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = 863f330cc1a1259ed5a5998a23acfd37fb4351a793a5b3c090b
642ddc439b945
EvaluationElement = aa8fa048764d5623868679402ff6108d2521884fa138cd7f
9c7669a9a014267e
Proof = ddef93772692e535d1a53903db24367355cc2cc78de93b3be5a8ffcc6985
dd066d4346421d17bf5117a2a1ff0fcb2a759f58a539dfbe857a40bce4cf49ec600d
ProofRandomScalar = 222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d98
81aa6f61d645fc0e
Output = b58cfbe118e0cb94d79b5fd6a6dafb98764dff49c14e1770b566e42402d
a1a7da4d8527693914139caee5bd03903af43a491351d23b430948dd50cde10d32b3
c
A.1.2.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = cc0b2a350101881d8a4cba4c80241d74fb7dcbfde4a61fde2f9
1443c2bf9ef0c
EvaluationElement = 60a59a57208d48aca71e9e850d22674b611f752bed48b36f
7a91b372bd7ad468
Proof = 401a0da6264f8cf45bb2f5264bc31e109155600babb3cd4e5af7d181a2c9
dc0a67154fabf031fd936051dec80b0b6ae29c9503493dde7393b722eafdf5a50b02
ProofRandomScalar = 222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d98
81aa6f61d645fc0e
Output = 8a9a2f3c7f085b65933594309041fc1898d42d0858e59f90814ae90571a
6df60356f4610bf816f27afdd84f47719e480906d27ecd994985890e5f539e7ea74b
6
A.1.2.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706,222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d9881aa6f61d645fc0
e
BlindedElement = 863f330cc1a1259ed5a5998a23acfd37fb4351a793a5b3c090b
642ddc439b945,90a0145ea9da29254c3a56be4fe185465ebb3bf2a1801f7124bbba
dac751e654
EvaluationElement = aa8fa048764d5623868679402ff6108d2521884fa138cd7f
9c7669a9a014267e,cc5ac221950a49ceaa73c8db41b82c20372a4c8d63e5dded2db
920b7eee36a2a
Proof = cc203910175d786927eeb44ea847328047892ddf8590e723c37205cb7460
0b0a5ab5337c8eb4ceae0494c2cf89529dcf94572ed267473d567aeed6ab873dee08
ProofRandomScalar = 419c4f4f5052c53c45f3da494d2b67b220d02118e0857cdb
cf037f9ea84bbe0c
Output = b58cfbe118e0cb94d79b5fd6a6dafb98764dff49c14e1770b566e42402d
a1a7da4d8527693914139caee5bd03903af43a491351d23b430948dd50cde10d32b3
c,8a9a2f3c7f085b65933594309041fc1898d42d0858e59f90814ae90571a6df6035
6f4610bf816f27afdd84f47719e480906d27ecd994985890e5f539e7ea74b6
A.1.3. POPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 145c79c108538421ac164ecbe131942136d5570b16d8bf41a24d4337da981
e07
pkSm = c647bef38497bc6ec077c22af65b696efa43bff3b4a1975a3e8e0a1c5a79d
631
A.1.3.1. Test Vector 1, Batch Size 1
Input = 00
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = c8713aa89241d6989ac142f22dba30596db635c772cbf25021f
dd8f3d461f715
EvaluationElement = 1a4b860d808ff19624731e67b5eff20ceb2df3c3c03b906f
5693e2078450d874
Proof = 41ad1a291aa02c80b0915fbfbb0c0afa15a57e2970067a602ddb9e8fd6b7
100de32e1ecff943a36f0b10e3dae6bd266cdeb8adf825d86ef27dbc6c0e30c52206
ProofRandomScalar = 222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d98
81aa6f61d645fc0e
Output = ca688351e88afb1d841fde4401c79efebb2eb75e7998fa9737bd5a82a15
2406d38bd29f680504e54fd4587eddcf2f37a2617ac2fbd2993f7bdf45442ace7d22
1
A.1.3.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706
BlindedElement = f0f0b209dd4d5f1844dac679acc7761b91a2e704879656cb7c2
01e82a99ab07d
EvaluationElement = 8c3c9d064c334c6991e99f286ea2301d1bde170b54003fb9
c44c6d7bd6fc1540
Proof = 4c39992d55ffba38232cdac88fe583af8a85441fefd7d1d4a8d0394cd1de
77018bf135c174f20281b3341ab1f453fe72b0293a7398703384bed822bfdeec8908
ProofRandomScalar = 222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d98
81aa6f61d645fc0e
Output = 7c6557b276a137922a0bcfc2aa2b35dd78322bd500235eb6d6b6f91bc5b
56a52de2d65612d503236b321f5d0bebcbc52b64b92e426f29c9b8b69f52de98ae50
7
A.1.3.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec4c1f
6706,222a5e897cf59db8145db8d16e597e8facb80ae7d4e26d9881aa6f61d645fc0
e
BlindedElement = c8713aa89241d6989ac142f22dba30596db635c772cbf25021f
dd8f3d461f715,423a01c072e06eb1cce96d23acce06e1ea64a609d7ec9e9023f304
9f2d64e50c
EvaluationElement = 1a4b860d808ff19624731e67b5eff20ceb2df3c3c03b906f
5693e2078450d874,aa1f16e903841036e38075da8a46655c94fc92341887eb5819f
46312adfc0504
Proof = 43fdb53be399cbd3561186ae480320caa2b9f36cca0e5b160c4a677b8bbf
4301b28f12c36aa8e11e5a7ef551da0781e863a6dc8c0b2bf5a149c9e00621f02006
ProofRandomScalar = 419c4f4f5052c53c45f3da494d2b67b220d02118e0857cdb
cf037f9ea84bbe0c
Output = ca688351e88afb1d841fde4401c79efebb2eb75e7998fa9737bd5a82a15
2406d38bd29f680504e54fd4587eddcf2f37a2617ac2fbd2993f7bdf45442ace7d22
1,7c6557b276a137922a0bcfc2aa2b35dd78322bd500235eb6d6b6f91bc5b56a52de
2d65612d503236b321f5d0bebcbc52b64b92e426f29c9b8b69f52de98ae507
A.2. decaf448-SHAKE256
A.2.1. OPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = e8b1375371fd11ebeb224f832dcc16d371b4188951c438f751425699ed29e
cc80c6c13e558ccd67634fd82eac94aa8d1f0d7fee990695d1e
A.2.1.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = e0ae01c4095f08e03b19baf47ffdc19cb7d98e583160522a3c7
d6a0b2111cd93a126a46b7b41b730cd7fc943d4e28e590ed33ae475885f6c
EvaluationElement = 50ce4e60eed006e22e7027454b5a4b8319eb2bc8ced609eb
19eb3ad42fb19e06ba12d382cbe7ae342a0cad6ead0ef8f91f00bb7f0cd9c0a2
Output = 37d3f7922d9388a15b561de5829bbf654c4089ede89c0ce0f3f85bcdba0
9e382ce0ab3507e021f9e79706a1798ffeac68ebd5cf62e5eb9838c7068351d97ae3
7
A.2.1.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = 86a88dc5c6331ecfcb1d9aacb50a68213803c462e377577cacc
00af28e15f0ddbc2e3d716f2f39ef95f3ec1314a2c64d940a9f295d8f13bb
EvaluationElement = 162e9fa6e9d527c3cd734a31bf122a34dbd5bcb7bb23651f
1768a7a9274cc116c03b58afa6f0dede3994a60066c76370e7328e7062fd5819
Output = a2a652290055cb0f6f8637a249ee45e32ef4667db0b4c80c0a70d2a6416
4d01525cfdad5d870a694ec77972b9b6ec5d2596a5223e5336913f945101f0137f55
e
A.2.2. VOPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = e3c01519a076a326a0eb566343e9b21c115fa18e6e85577ddbe890b33104f
cc2835ddfb14a928dc3f5d79b936e17c76b99e0bf6a1680930e
pkSm = 945fc518c47695cf65217ace04b86ac5e4cbe26ca649d52854bb16c494ce0
9069d6add96b20d4b0ae311a87c9a73e3a146b525763ab2f955
A.2.2.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = 7261bbc335c664ba788f1b1a1a4cd5190cc30e787ef277665ac
1d314f8861e3ec11854ce3ddd42035d9e0f5cddde324c332d8c880abc00eb
EvaluationElement = ca1491a526c28d880806cf0fb0122222392cf495657be6e4
c9d203bceffa46c86406caf8217859d3fb259077af68e5d41b3699410781f467
Proof = f84bbeee47aedf43558dae4b95b3853635a9fc1a9ea7eac9b454c64c66c4
f49cd1c72711c7ac2e06c681e16ea693d5500bbd7b56455df52f69e00b76b4126961
e1562fdbaaac40b7701065cbeece3febbfe09e00160f81775d36daed99d8a2a10be0
759e01b7ee81217203416c9db208
ProofRandomScalar = b1b748135d405ce48c6973401d9455bb8ccd18b01d0295c0
627f67661200dbf9569f73fbb3925daa043a070e5f953d80bb464ea369e5522b
Output = e2ac40b634f36cccd8262b285adff7c9dcc19cd308564a5f4e581d1a853
5773b86fa4fc9f2203c370763695c5093aea4a7aedec4488b1340ba3bf663a23098c
1
A.2.2.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = 88287e553939090b888ddc15913e1807dc4757215555e1c3a79
488ef311594729c7fa74c772a732b78440b7d66d0aa35f3bb316f1d93e1b2
EvaluationElement = c00978c73e8e4ee1d447ab0d3ad1754055e72cc85c08e3a0
db170909a9c61cbff1f1e7015f289e3038b0f341faea5d7780c130106065c231
Proof = 7a2831a6b237e11ac1657d440df93bc5ce00f552e6020a99d5c956ffc4d0
7b5ade3e82ecdc257fd53d76239e733e0a1313e84ce16cc0d82734806092a693d7e8
d3c420c2cb6ccd5d0ca32514fb78e9ad0973ebdcb52eba438fc73948d76339ee7101
21d83e2fe6f001cfdf551aff9f36
ProofRandomScalar = b1b748135d405ce48c6973401d9455bb8ccd18b01d0295c0
627f67661200dbf9569f73fbb3925daa043a070e5f953d80bb464ea369e5522b
Output = 862952380e07ec840d9f6e6f909c5a25d16c3dacb586d89a181b4aa7380
c959baa8c480fe8e6c64e089d68ea7aeeb5817bd524d7577905b5bab487690048c94
1
A.2.2.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112,b1b748135d405ce
48c6973401d9455bb8ccd18b01d0295c0627f67661200dbf9569f73fbb3925daa043
a070e5f953d80bb464ea369e5522b
BlindedElement = 7261bbc335c664ba788f1b1a1a4cd5190cc30e787ef277665ac
1d314f8861e3ec11854ce3ddd42035d9e0f5cddde324c332d8c880abc00eb,2e15f3
93c035492a1573627a3606e528c6294c767c8d43b8c691ef70a52cc7dc7d1b53fe45
8350a270abb7c231b87ba58266f89164f714d9
EvaluationElement = ca1491a526c28d880806cf0fb0122222392cf495657be6e4
c9d203bceffa46c86406caf8217859d3fb259077af68e5d41b3699410781f467,8ec
68e9871b296e81c55647ce64a04fe75d19932f1400544cd601468c60f998408bbb54
6601d4a636e8be279e558d70b95c8d4a4f61892be
Proof = 167d922f0a6ffa845eed07f8aa97b6ac746d902ecbeb18f49c009adc0521
eab1e4d275b74a2dc266b7a194c854e85e7eb54a9a36376dfc04ec7f3bd55fc9618c
3970cb548e064f8a2f06183a5702933dbc3e4c25a73438f2108ee1981c306181003c
7ea92fce963ec7b4ba4f270e6d38
ProofRandomScalar = 63798726803c9451ba405f00ef3acb633ddf0c420574a2ec
6cbf28f840800e355c9fbaac10699686de2724ed22e797a00f3bd93d105a7f23
Output = e2ac40b634f36cccd8262b285adff7c9dcc19cd308564a5f4e581d1a853
5773b86fa4fc9f2203c370763695c5093aea4a7aedec4488b1340ba3bf663a23098c
1,862952380e07ec840d9f6e6f909c5a25d16c3dacb586d89a181b4aa7380c959baa
8c480fe8e6c64e089d68ea7aeeb5817bd524d7577905b5bab487690048c941
A.2.3. POPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 792a10dcbd3ba4a52a054f6f39186623208695301e7adb9634b74709ab22d
e402990eb143fd7c67ac66be75e0609705ecea800992aac8e19
pkSm = 6c9d12723a5bbcf305522cc04b4a34d9ced2e12831826018ea7b5dcf54526
47ad262113059bf0f6e4354319951b9d513c74f29cb0eec38c1
A.2.3.1. Test Vector 1, Batch Size 1
Input = 00
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = 161183c13c6cb33b0e4f9b7365f8c5c12d13c72f8b62d276ca0
9368d093dce9b42198276b9e9d870ac392dda53efd28d1b7e6e8c060cdc42
EvaluationElement = 06ec89dfde25bb2a6f0145ac84b91ac277b35de39ad1d6f4
02a8e46414952ce0d9ea1311a4ece283e2b01558c7078b040cfaa40dd63b3e6c
Proof = 66caee75bf2460429f620f6ad3e811d524cb8ddd848a435fc5d89af48877
abf6506ee341a0b6f67c2d76cd021e5f3d1c9abe5aa9f0dce016da746135fedba2af
41ed1d01659bfd6180d96bc1b7f320c0cb6926011ce392ecca748662564892bae665
16acaac6ca39aadf6fcca95af406
ProofRandomScalar = b1b748135d405ce48c6973401d9455bb8ccd18b01d0295c0
627f67661200dbf9569f73fbb3925daa043a070e5f953d80bb464ea369e5522b
Output = 4423f6dcc1740688ea201de57d76824d59cd6b859e1f9884b7eebc49b0b
971358cf9cb075df1536a8ea31bcf55c3e31c2ba9cfa8efe54448d17091daeb9924e
d
A.2.3.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112
BlindedElement = 12082b6a381c6c51e85d00f2a3d828cdeab3f5cb19a10b9c014
c33826764ab7e7cfb8b4ff6f411bddb2d64e62a472af1cd816e5b712790c6
EvaluationElement = f2919b7eedc05ab807c221fce2b12c4ae9e19e6909c47845
64b690d1972d2994ca623f273afc67444d84ea40cbc58fcdab7945f321a52848
Proof = a295677c54d1bc4286330907fc2490a7de163da26f9ce03a462a452fea42
2b19ade296ba031359b3b6841e48455d20519ad01b4ac4f0b92e76d3cf16fbef0a3f
72791a8401ef2d7081d361e502e96b2c60608b9fa566f43d4611c2f161d83aabef7f
8017332b26ed1daaf80440772022
ProofRandomScalar = b1b748135d405ce48c6973401d9455bb8ccd18b01d0295c0
627f67661200dbf9569f73fbb3925daa043a070e5f953d80bb464ea369e5522b
Output = 8691905500510843902c44bdd9730ab9dc3925aa58ff9dd42765a2baf63
3126de0c3adb93bef5652f38e5827b6396e87643960163a560fc4ac9738c8de4e4a8
d
A.2.3.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 64d37aed22a27f5191de1c1d69fadb899d8862b58eb4220029e036ec65fa
3833a26e9388336361686ff1f83df55046504dfecad8549ba112,b1b748135d405ce
48c6973401d9455bb8ccd18b01d0295c0627f67661200dbf9569f73fbb3925daa043
a070e5f953d80bb464ea369e5522b
BlindedElement = 161183c13c6cb33b0e4f9b7365f8c5c12d13c72f8b62d276ca0
9368d093dce9b42198276b9e9d870ac392dda53efd28d1b7e6e8c060cdc42,fc8847
d43fb4cea4e408f585661a8f2867533fa91d22155d3127a22f18d3b007add480f7d3
00bca93fa47fe87ae06a57b7d0f0d4c30b12f0
EvaluationElement = 06ec89dfde25bb2a6f0145ac84b91ac277b35de39ad1d6f4
02a8e46414952ce0d9ea1311a4ece283e2b01558c7078b040cfaa40dd63b3e6c,2e7
4c626d07de49b1c8c21d87120fd78105f485e36816af9bde3e3efbeef76815326062
fd333925b66c5ce5a20f100bf01770c16609f990a
Proof = fd94db736f97ea4efe9d0d4ad2933072697a6bbeb32834057b23edf7c700
9f011dfa72157f05d2a507c2bbf0b54cad99ab99de05921c021fda7d70e65bcecdb0
5f9a30154127ace983c74d10fd910b554c5e95f6bd1565fd1f3dbbe3c523ece5c72d
57a559b7be1368c4786db4a3c910
ProofRandomScalar = 63798726803c9451ba405f00ef3acb633ddf0c420574a2ec
6cbf28f840800e355c9fbaac10699686de2724ed22e797a00f3bd93d105a7f23
Output = 4423f6dcc1740688ea201de57d76824d59cd6b859e1f9884b7eebc49b0b
971358cf9cb075df1536a8ea31bcf55c3e31c2ba9cfa8efe54448d17091daeb9924e
d,8691905500510843902c44bdd9730ab9dc3925aa58ff9dd42765a2baf633126de0
c3adb93bef5652f38e5827b6396e87643960163a560fc4ac9738c8de4e4a8d
A.3. P256-SHA256
A.3.1. OPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 159749d750713afe245d2d39ccfaae8381c53ce92d098a9375ee70739c7ac
0bf
A.3.1.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 03723a1e5c09b8b9c18d1dcbca29e8007e95f14f4732d9346d4
90ffc195110368d
EvaluationElement = 030de02ffec47a1fd53efcdd1c6faf5bdc270912b8749e78
3c7ca75bb412958832
Output = a0b34de5fa4c5b6da07e72af73cc507cceeb48981b97b7285fc375345fe
495dd
A.3.1.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 03cc1df781f1c2240a64d1c297b3f3d16262ef5d4cf10273488
2675c26231b0838
EvaluationElement = 03a0395fe3828f2476ffcd1f4fe540e5a8489322d398be3c
4e5a869db7fcb7c52c
Output = c748ca6dd327f0ce85f4ae3a8cd6d4d5390bbb804c9e12dcf94f853fece
3dcce
A.3.2. VOPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = ca5d94c8807817669a51b196c34c1b7f8442fde4334a7121ae4736364312f
ca6
pkSm = 03e17e70604bcabe198882c0a1f27a92441e774224ed9c702e51dd17038b1
02462
A.3.2.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 02dd05901038bb31a6fae01828fd8d0e49e35a486b5c5d4b499
4013648c01277da
EvaluationElement = 0209f33cab60cf8fe69239b0afbcfcd261af4c1c5632624f
2e9ba29b90ae83e4a2
Proof = e7c2b3c5c954c035949f1f74e6bce2ed539a3be267d1481e9ddb178533df
4c2664f69d065c604a4fd953e100b856ad83804eb3845189babfa5a702090d6fc5fa
ProofRandomScalar = f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 0412e8f78b02c415ab3a288e228978376f99927767ff37c5718d420010a
645a1
A.3.2.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 03cd0f033e791c4d79dfa9c6ed750f2ac009ec46cd4195ca6fd
3800d1e9b887dbd
EvaluationElement = 030d2985865c693bf7af47ba4d3a3813176576383d19aff0
03ef7b0784a0d83cf1
Proof = 2787d729c57e3d9512d3aa9e8708ad226bc48e0f1750b0767aaff73482c4
4b8d2873d74ec88aebd3504961acea16790a05c542d9fbff4fe269a77510db00abab
ProofRandomScalar = f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 771e10dcd6bcd3664e23b8f2a710cfaaa8357747c4a8cbba03133967b5c
24f18
A.3.2.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364,f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b
1
BlindedElement = 02dd05901038bb31a6fae01828fd8d0e49e35a486b5c5d4b499
4013648c01277da,03462e9ae64cae5b83ba98a6b360d942266389ac369b923eb3d5
57213b1922f8ab
EvaluationElement = 0209f33cab60cf8fe69239b0afbcfcd261af4c1c5632624f
2e9ba29b90ae83e4a2,02bb24f4d838414aef052a8f044a6771230ca69c0a5677540
fff738dd31bb69771
Proof = bdcc351707d02a72ce49511c7db990566d29d6153ad6f8982fad2b435d6c
e4d60da1e6b3fa740811bde34dd4fe0aa1b5fe6600d0440c9ddee95ea7fad7a60cf2
ProofRandomScalar = 350e8040f828bf6ceca27405420cdf3d63cb3aef005f40ba
51943c8026877963
Output = 0412e8f78b02c415ab3a288e228978376f99927767ff37c5718d420010a
645a1,771e10dcd6bcd3664e23b8f2a710cfaaa8357747c4a8cbba03133967b5c24f
18
A.3.3. POPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 6ad2173efa689ef2c27772566ad7ff6e2d59b3b196f00219451fb2c89ee4d
ae2
pkSm = 030d7ff077fddeec965db14b794f0cc1ba9019b04a2f4fcc1fa525dedf72e
2a3e3
A.3.3.1. Test Vector 1, Batch Size 1
Input = 00
Info = 7465737420696e666f
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 031563e127099a8f61ed51eeede05d747a8da2be329b40ba1f0
db0b2bd9dd4e2c0
EvaluationElement = 02c5e5300c2d9e6ba7f3f4ad60500ad93a0157e6288eb04b
67e125db024a2c74d2
Proof = f8a33690b87736c854eadfcaab58a59b8d9c03b569110b6f31f8bf7577f3
fbb85a8a0c38468ccde1ba942be501654adb106167c8eb178703ccb42bccffb9231a
ProofRandomScalar = f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 193a92520bd8fd1f37accb918040a57108daa110dc4f659abe212636d24
5c592
A.3.3.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 021a440ace8ca667f261c10ac7686adc66a12be31e3520fca31
7643a1eee9dcd4d
EvaluationElement = 0208ca109cbae44f4774fc0bdd2783efdcb868cb4523d521
96f700210e777c5de3
Proof = 043a8fb7fc7fd31e35770cabda4753c5bf0ecc1e88c68d7d35a62bf2631e
875af4613641be2d1875c31d1319d191c4bbc0d04875f4fd03c31d3d17dd8e069b69
ProofRandomScalar = f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 1e6d164cfd835d88a31401623549bf6b9b306628ef03a7962921d62bc5f
fce8c
A.3.3.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 3338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364,f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b
1
BlindedElement = 031563e127099a8f61ed51eeede05d747a8da2be329b40ba1f0
db0b2bd9dd4e2c0,03ca4ff41c12fadd7a0bc92cf856732b21df652e01a3abdf0fa8
847da053db213c
EvaluationElement = 02c5e5300c2d9e6ba7f3f4ad60500ad93a0157e6288eb04b
67e125db024a2c74d2,02f0b6bcd467343a8d8555a99dc2eed0215c71898c5edb77a
3d97ddd0dbad478e8
Proof = 8fbd85a32c13aba79db4b42e762c00687d6dbf9c8cb97b2a225645ccb00d
9d7580b383c885cdfd07df448d55e06f50f6173405eee5506c0ed0851ff718d13e68
ProofRandomScalar = 350e8040f828bf6ceca27405420cdf3d63cb3aef005f40ba
51943c8026877963
Output = 193a92520bd8fd1f37accb918040a57108daa110dc4f659abe212636d24
5c592,1e6d164cfd835d88a31401623549bf6b9b306628ef03a7962921d62bc5ffce
8c
A.4. P384-SHA384
A.4.1. OPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = dfe7ddc41a4646901184f2b432616c8ba6d452f9bcd0c4f75a5150ef2b2ed
02ef40b8b92f60ae591bcabd72a6518f188
A.4.1.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 02a36bc90e6db34096346eaf8b7bc40ee1113582155ad379700
3ce614c835a874343701d3f2debbd80d97cbe45de6e5f1f
EvaluationElement = 03af2a4fc94770d7a7bf3187ca9cc4faf3732049eded2442
ee50fbddda58b70ae2999366f72498cdbc43e6f2fc184afe30
Output = ed84ad3f31a552f0456e58935fcc0a3039db42e7f356dcb32aa6d487b6b
815a07d5813641fb1398c03ddab5763874357
A.4.1.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 02def6f418e3484f67a124a2ce1bfb19de7a4af568ede6a1ebb
2733882510ddd43d05f2b1ab5187936a55e50a847a8b900
EvaluationElement = 034e9b9a2960b536f2ef47d8608b21597ba400d5abfa1825
fd21c36b75f927f396bf3716c96129d1fa4a77fa1d479c8d7b
Output = dd4f29da869ab9355d60617b60da0991e22aaab243a3460601e48b07585
9d1c526d36597326f1b985778f781a1682e75
A.4.2. VOPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 051646b9e6e7a71ae27c1e1d0b87b4381db6d3595eeeb1adb41579adbf992
f4278f9016eafc944edaa2b43183581779d
pkSm = 031d689686c611991b55f1a1d8f4305ccd6cb719446f660a30db61b7aa87b
46acf59b7c0d4a9077b3da21c25dd482229a0
A.4.2.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 02d338c05cbecb82de13d6700f09cb61190543a7b7e2c6cd4fc
a56887e564ea82653b27fdad383995ea6d02cf26d0e24d9
EvaluationElement = 02a7bba589b3e8672aa19e8fd258de2e6aae20101c8d7612
46de97a6b5ee9cf105febce4327a326255a3c604f63f600ef6
Proof = bfc6cf3859127f5fe25548859856d6b7fa1c7459f0ba5712a806fc091a30
00c42d8ba34ff45f32a52e40533efd2a03bc87f3bf4f9f58028297ccb9ccb18ae718
2bcd1ef239df77e3be65ef147f3acf8bc9cbfc5524b702263414f043e3b7ca2e
ProofRandomScalar = 803d955f0e073a04aa5d92b3fb739f56f9db001266677f62
c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
Output = 3333230886b562ffb8329a8be08fea8025755372817ec969d114d1203d0
26b4a622beab60220bf19078bca35a529b35c
A.4.2.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 02f27469e059886f221be5f2cca03d2bdc61e55221721c3b3e5
6fc012e36d31ae5f8dc058109591556a6dbd3a8c69c433b
EvaluationElement = 03f16f903947035400e96b7f531a38d4a07ac89a80f89d86
a1bf089c525a92c7f4733729ca30c56ce78b1ab4f7d92db8b4
Proof = d005d6daaad7571414c1e0c75f7e57f2113ca9f4604e84bc90f9be52da89
6fff3bee496dcde2a578ae9df315032585f801fb21c6080ac05672b291e575a40295
b306d967717b28e08fcc8ad1cab47845d16af73b3e643ddcc191208e71c64630
ProofRandomScalar = 803d955f0e073a04aa5d92b3fb739f56f9db001266677f62
c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
Output = b91c70ea3d4d62ba922eb8a7d03809a441e1c3c7af915cbc2226f485213
e895942cd0f8580e6d99f82221e66c40d274f
A.4.2.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364,803d955f0e073a04aa5d92b3fb739f5
6f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
BlindedElement = 02d338c05cbecb82de13d6700f09cb61190543a7b7e2c6cd4fc
a56887e564ea82653b27fdad383995ea6d02cf26d0e24d9,02fa02470d7f151018b4
1e82223c32fad824de6ad4b5ce9f8e9f98083c9a726de9a1fc39d7a0cb6f4f188dd9
cea01474cd
EvaluationElement = 02a7bba589b3e8672aa19e8fd258de2e6aae20101c8d7612
46de97a6b5ee9cf105febce4327a326255a3c604f63f600ef6,028e9e115625ff4c2
f07bf87ce3fd73fc77994a7a0c1df03d2a630a3d845930e2e63a165b114d98fe34e6
1b68d23c0b50a
Proof = 6d8dcbd2fc95550a02211fb78afd013933f307d21e7d855b0b1ed0af7807
6d8137ad8b0a1bfa05676d325249c1dbb9a52bd81b1c2b7b0efc77cf7b278e1c947f
6283f1d4c513053fc0ad19e026fb0c30654b53d9cea4b87b037271b5d2e2d0ea
ProofRandomScalar = a097e722ed2427de86966910acba9f5c350e8040f828bf6c
eca27405420cdf3d63cb3aef005f40ba51943c8026877963
Output = 3333230886b562ffb8329a8be08fea8025755372817ec969d114d1203d0
26b4a622beab60220bf19078bca35a529b35c,b91c70ea3d4d62ba922eb8a7d03809
a441e1c3c7af915cbc2226f485213e895942cd0f8580e6d99f82221e66c40d274f
A.4.3. POPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 5b2690d6954b8fbb159f19935d64133f12770c00b68422559c65431942d72
1ff79d47d7a75906c30b7818ec0f38b7fb2
pkSm = 02f00f0f1de81e5d6cf18140d4926ffdc9b1898c48dc49657ae36eb1e45de
b8b951aaf1f10c82d2eaa6d02aafa3f10d2b6
A.4.3.1. Test Vector 1, Batch Size 1
Input = 00
Info = 7465737420696e666f
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 03859b36b95e6564faa85cd3801175eda2949707f6aa0640ad0
93cbf8ad2f58e762f08b56b2a1b42a64953aaf49cbf1ae3
EvaluationElement = 0220710e2e00306453f5b4f574cb6a512453f35c45080d09
373e190c19ce5b185914fbf36582d7e0754bb7c8b683205b91
Proof = 82a17ef41c8b57f1e3122311b4d5cd39a63df0f67443ef18d961f9b659c1
601ced8d3c64b294f604319ca80230380d437a49c7af0d620e22116669c008ebb767
d90283d573b49cdb49e3725889620924c2c4b047a2a6225a3ba27e640ebddd33
ProofRandomScalar = 803d955f0e073a04aa5d92b3fb739f56f9db001266677f62
c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
Output = 0188653cfec38119a6c7dd7948b0f0720460b4310e40824e048bf82a165
27303ed449a08caf84272c3bbc972ede797df
A.4.3.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364
BlindedElement = 03f7efcb4aaf000263369d8a0621cb96b81b3206e99876de2a0
0699ed4c45acf3969cd6e2319215395955d3f8d8cc1c712
EvaluationElement = 034993c818369927e74b77c400376fd1ae29b6ac6c6ddb77
6cf10e4fbc487826531b3cf0b7c8ca4d92c7af90c9def85ce6
Proof = 693471b5dff0cd6a5c00ea34d7bf127b2795164e3bdb5f39a1e5edfbd13e
443bc516061cd5b8449a473c2ceeccada9f3e5b57302e3d7bc5e28d38d6e3a3056e1
e73b6cc030f5180f8a1ffa45aa923ee66d2ad0a07b500f2acc7fb99b5506465c
ProofRandomScalar = 803d955f0e073a04aa5d92b3fb739f56f9db001266677f62
c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
Output = ff2a527a21cc43b251a567382677f078c6e356336aec069dea8ba369953
43ca3b33bb5d6cf15be4d31a7e6d75b30d3f5
A.4.3.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 504650f53df8f16f6861633388936ea23338fa65ec36e0290022b48eb562
889d89dbfa691d1cde91517fa222ed7ad364,803d955f0e073a04aa5d92b3fb739f5
6f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b1
BlindedElement = 03859b36b95e6564faa85cd3801175eda2949707f6aa0640ad0
93cbf8ad2f58e762f08b56b2a1b42a64953aaf49cbf1ae3,021a65d618d645f1a20b
c33b06deaa7e73d6d634c8a56a3d02b53a732b69a5c53c5a207ea33d5afdcde9a22d
59726bce51
EvaluationElement = 0220710e2e00306453f5b4f574cb6a512453f35c45080d09
373e190c19ce5b185914fbf36582d7e0754bb7c8b683205b91,02017657b315ec65e
f861505e596c8645d94685dd7602cdd092a8f1c1c0194a5d0485fe47d071d972ab51
4370174cc23f5
Proof = 4a0b2fe96d5b2a046a0447fe079b77859ef11a39a3520d6ff7c626aad9b4
73b724fb0cf188974ec961710a62162a83e97e0baa9eeada73397032d928b3e97b1e
a92ad9458208302be3681b8ba78bcc17745bac00f84e0fdc98a6a8cba009c080
ProofRandomScalar = a097e722ed2427de86966910acba9f5c350e8040f828bf6c
eca27405420cdf3d63cb3aef005f40ba51943c8026877963
Output = 0188653cfec38119a6c7dd7948b0f0720460b4310e40824e048bf82a165
27303ed449a08caf84272c3bbc972ede797df,ff2a527a21cc43b251a567382677f0
78c6e356336aec069dea8ba36995343ca3b33bb5d6cf15be4d31a7e6d75b30d3f5
A.5. P521-SHA512
A.5.1. OPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 0153441b8faedb0340439036d6aed06d1217b34c42f17f8db4c5cc610a4a9
55d698a688831b16d0dc7713a1aa3611ec60703bffc7dc9c84e3ed673b3dbe1d5fcc
ea6
A.5.1.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 0300e78bf846b0e1e1a3c320e353d758583cd876df56100a3a1
e62bacba470fa6e0991be1be80b721c50c5fd0c672ba764457acc18c6200704e9294
fbf28859d916351
EvaluationElement = 030166371cf827cb2fb9b581f97907121a16e2dc5d8b10ce
9f0ede7f7d76a0d047657735e8ad07bcda824907b3e5479bd72cdef6b839b967ba5c
58b118b84d26f2ba07
Output = 26232de6fff83f812adadadb6cc05d7bbeee5dca043dbb16b03488abb99
81d0a1ef4351fad52dbd7e759649af393348f7b9717566c19a6b8856284d69375c80
9
A.5.1.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 0300c28e57e74361d87e0c1874e5f7cc1cc796d61f9cad50427
cf54655cdb455613368d42b27f94bf66f59f53c816db3e95e68e1b113443d66a99b3
693bab88afb556b
EvaluationElement = 0301ad453607e12d0cc11a3359332a40c3a254eaa1afc642
96528d55bed07ba322e72e22cf3bcb50570fd913cb54f7f09c17aff8787af75f6a7f
af5640cbb2d9620a6e
Output = ad1f76ef939042175e007738906ac0336bbd1d51e287ebaa66901abdd32
4ea3ffa40bfc5a68e7939c2845e0fd37a5a6e76dadb9907c6cc8579629757fd4d04b
a
A.5.2. VOPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 015c7fc1b4a0b1390925bae915bd9f3d72009d44d9241b962428aad5d13f2
2803311e7102632a39addc61ea440810222715c9d2f61f03ea424ec9ab1fe5e31cf9
238
pkSm = 0301505d646f6e4c9102451eb39730c4ba1c4087618641edbdba4a60896b0
7fd0c9414ce553cbf25b81dfcca50a8f6724ab7a2bc4d0cf736967a287bb6084cc06
78ac0
A.5.2.1. Test Vector 1, Batch Size 1
Input = 00
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 0301d6e4fb545e043ddb6aee5d5ceeee1b44102615ab04430c2
7dd0f56988dedcb1df32ef384f160e0e76e718605f14f3f582f9357553d153b99679
5b4b3628a4f6380
EvaluationElement = 03013fdeaf887f3d3d283a79e696a54b66ff0edcb559265e
204a958acf840e0930cc147e2a6835148d8199eebc26c03e9394c9762a1c991dde40
bca0f8ca003eefb045
Proof = 0077fcc8ec6d059d7759b0a61f871e7c1dadc65333502e09a51994328f79
e5bda3357b9a4f410a1760a3612c2f8f27cb7cb032951c047cc66da60da583df7b24
7edd0188e5eb99c71799af1d80d643af16ffa1545acd9e9233fbb370455b10eb257e
a12a1667c1b4ee5b0ab7c93d50ae89602006960f083ca9adc4f6276c0ad60440393c
ProofRandomScalar = 015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e07
3a04aa5d92b3fb739f56f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 5e003d9b2fb540b3d4bab5fedd154912246da1ee5e557afd8f56415faa1
a0fadff6517da802ee254437e4f60907b4cda146e7ba19e249eef7be405549f62954
b
A.5.2.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 03005b05e656cb609ce5ff5faf063bb746d662d67bbd07c0626
38396f52f0392180cf2365cabb0ece8e19048961d35eeae5d5fa872328dce98df076
ee154dd191c615e
EvaluationElement = 0301b19fcf482b1fff04754e282292ed736c5f0aa080d4f4
2663cd3a416c6596f03129e8e096d8671fe5b0d19838312c511d2ce08d431e43e3ef
06199d8cab7426238d
Proof = 01ec9fece444caa6a57032e8963df0e945286f88fbdf233fb5101f0924f7
ea89c47023f5f72f240e61991fd33a299b5b38c45a5e2dd1a67b072e59dfe86708a3
59c701e38d383c60cf6969463bcf13251bedad47b7941f52e409a3591398e2792441
0b18a301c0e19f527cad504fa08388050ac634e1b05c5216d337742f2754e1fc502f
ProofRandomScalar = 015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e07
3a04aa5d92b3fb739f56f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = fa15eebba81ecf40954f7135cb76f69ef22c6bae394d1a4362f9b03066b
54b6604d39f2e53369ca6762a3d9787e230e832aa85955af40ecb8deebb009a8cf47
4
A.5.2.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364,015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e073a04aa5d92b3fb7
39f56f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b
1
BlindedElement = 0301d6e4fb545e043ddb6aee5d5ceeee1b44102615ab04430c2
7dd0f56988dedcb1df32ef384f160e0e76e718605f14f3f582f9357553d153b99679
5b4b3628a4f6380,0301403b597538b939b450c93586ba275f9711ba07e42364bac1
d5769c6824a8b55be6f9a536df46d952b11ab2188363b3d6737635d9543d4dba14a6
e19421b9245bf5
EvaluationElement = 03013fdeaf887f3d3d283a79e696a54b66ff0edcb559265e
204a958acf840e0930cc147e2a6835148d8199eebc26c03e9394c9762a1c991dde40
bca0f8ca003eefb045,03001f96424497e38c46c904978c2fa1636c5c3dd2e634a85
d8a7265977c5dce1f02c7e6c118479f0751767b91a39cce6561998258591b5d7c1bb
02445a9e08e4f3e8d
Proof = 00b4d215c8405e57c7a4b53398caf55f1f1623aaeb22408ddb9ea2913090
9b3f95dbb1ff366e81e86e918f9f2fd8b80dbb344cd498c9499d112905e585417e00
68c600fe5dea18b389ef6c4cc062935607b8ccbbb9a84fba3143868a3e8a58efa0bf
6ca642804d09dc06e980f64837811227c4267b217f1099a4e28b0854f4e5ee659796
ProofRandomScalar = 01ec21c7bb69b0734cb48dfd68433dd93b0fa097e722ed24
27de86966910acba9f5c350e8040f828bf6ceca27405420cdf3d63cb3aef005f40ba
51943c8026877963
Output = 5e003d9b2fb540b3d4bab5fedd154912246da1ee5e557afd8f56415faa1
a0fadff6517da802ee254437e4f60907b4cda146e7ba19e249eef7be405549f62954
b,fa15eebba81ecf40954f7135cb76f69ef22c6bae394d1a4362f9b03066b54b6604
d39f2e53369ca6762a3d9787e230e832aa85955af40ecb8deebb009a8cf474
A.5.3. POPRF Mode
Seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
KeyInfo = 74657374206b6579
skSm = 014893130030ce69cf714f536498a02ff6b396888f9bb507985c32928c442
7d6d39de10ef509aca4240e8569e3a88debc0d392e3361bcd934cb9bdd59e339dff7
b27
pkSm = 0301de8ceb9ffe9237b1bba87c320ea0bebcfc3447fe6f278065c6c69886d
692d1126b79b6844f829940ace9b52a5e26882cf7cbc9e57503d4cca3cd834584729
f812a
A.5.3.1. Test Vector 1, Batch Size 1
Input = 00
Info = 7465737420696e666f
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 020095cff9d7ecf65bdfee4ea92d6e748d60b02de34ad98094f
82e25d33a8bf50138ccc2cc633556f1a97d7ea9438cbb394df612f041c485a515849
d5ebb2238f2f0e2
EvaluationElement = 0301408e9c5be3ffcc1c16e5ae8f8aa68446223b0804b119
62e856af5a6d1c65ebbb5db7278c21db4e8cc06d89a35b6804fb1738a295b691638a
f77aa1327253f26d01
Proof = 0106a89a61eee9dd2417d2849a8e2167bc5f56e3aed5a3ff23e22511fa1b
37a29ed44d1bbfd6907d99cfbc558a56aec709282415a864a281e49dc53792a4a638
a0660034306d64be12a94dcea5a6d664cf76681911c8b9a84d49bf12d4893307ec14
436bd05f791f82446c0de4be6c582d373627b51886f76c4788256e3da7ec8fa18a86
ProofRandomScalar = 015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e07
3a04aa5d92b3fb739f56f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 808ae5b87662eaaf0b39151dd85991b94c96ef214cb14a68bf5c1439548
82d330da8953a80eea20788e552bc8bbbfff3100e89f9d6e341197b122c46a208733
b
A.5.3.2. Test Vector 2, Batch Size 1
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364
BlindedElement = 030112ea89cf9cf589496189eafc5f9eb13c9f9e170d6ecde7c
5b940541cb1a9c5cfeec908b67efe16b81ca00d0ce216e34b3d5f46a658d3fd8573d
671bdb6515ed508
EvaluationElement = 0200ebc49df1e6fa61f412e6c391e6f074400ecdd2f56c4a
8c03fe0f91d9b551f40d4b5258fd891952e8c9b28003bcfa365122e54a5714c8949d
5d202767b31b4bf1f6
Proof = 0082162c71a7765005cae202d4bd14b84dae63c29067e886b82506992bd9
94a1c3aac0c1c5309222fe1af8287b6443ed6df5c2e0b0991faddd3564c73c7597ae
cd9a003b1f1e3c65f28e58ab4e767cfb4adbcaf512441645f4c2aed8bf67d132d966
006d35fa71a34145414bf3572c1de1a46c266a344dd9e22e7fb1e90ffba1caf556d9
ProofRandomScalar = 015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e07
3a04aa5d92b3fb739f56f9db001266677f62c095021db018cd8cbb55941d4073698c
e45c405d1348b7b1
Output = 27032e24b1a52a82ab7f4646f3c5df0f070f499db98b9c5df33972bd5af
5762c3638afae7912a6c1acdb1ae2ab2fa670bd5486c645a0e55412e08d33a4a0d6e
3
A.5.3.3. Test Vector 3, Batch Size 2
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 00d1dccf7a51bafaf75d4a866d53d8cafe4d504650f53df8f16f68616333
88936ea23338fa65ec36e0290022b48eb562889d89dbfa691d1cde91517fa222ed7a
d364,015e80ae32363b32cb76ad4b95a5a34e46bb803d955f0e073a04aa5d92b3fb7
39f56f9db001266677f62c095021db018cd8cbb55941d4073698ce45c405d1348b7b
1
BlindedElement = 020095cff9d7ecf65bdfee4ea92d6e748d60b02de34ad98094f
82e25d33a8bf50138ccc2cc633556f1a97d7ea9438cbb394df612f041c485a515849
d5ebb2238f2f0e2,0201a328cf9f3fdeb86b6db242dd4cbb436b3a488b70b72d2fbb
d1e5f50d7b0878b157d6f278c6a95c488f3ad52d6898a421658a82fe7ceb000b01ae
dea7967522d525
EvaluationElement = 0301408e9c5be3ffcc1c16e5ae8f8aa68446223b0804b119
62e856af5a6d1c65ebbb5db7278c21db4e8cc06d89a35b6804fb1738a295b691638a
f77aa1327253f26d01,020062ab51ac3aa829e0f5b7ae50688bcf5f63a18a83a6e0d
a538666b8d50c7ea2b4ef31f4ac669302318dbebe46660acdda695da30c22cee7ca2
1f6984a720504502e
Proof = 00731738844f739bca0cca9d1c8bea204bed4fd00285785738b985763741
de5cdfa275152d52b6a2fdf7792ef3779f39ba34581e56d62f78ecad5b7f8083f384
961501cd4b43713253c022692669cf076b1d382ecd8293c1de69ea569737f37a2477
2ab73517983c1e3db5818754ba1f008076267b8058b6481949ae346cdc17a8455fe2
ProofRandomScalar = 01ec21c7bb69b0734cb48dfd68433dd93b0fa097e722ed24
27de86966910acba9f5c350e8040f828bf6ceca27405420cdf3d63cb3aef005f40ba
51943c8026877963
Output = 808ae5b87662eaaf0b39151dd85991b94c96ef214cb14a68bf5c1439548
82d330da8953a80eea20788e552bc8bbbfff3100e89f9d6e341197b122c46a208733
b,27032e24b1a52a82ab7f4646f3c5df0f070f499db98b9c5df33972bd5af5762c36
38afae7912a6c1acdb1ae2ab2fa670bd5486c645a0e55412e08d33a4a0d6e3
"#;
+31 -24
View File
@@ -1,27 +1,28 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use alloc::vec::Vec;
use core::cmp::min;
use rand_core::{CryptoRng, Error, RngCore};
use core::convert::Infallible;
use rand_core::{TryCryptoRng, TryRng};
/// 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 }
}
@@ -38,29 +39,35 @@ fn rotate_left<T>(data: &mut [T], steps: usize) {
data.reverse();
}
impl RngCore for CycleRng {
fn next_u32(&mut self) -> u32 {
unimplemented!()
impl TryRng for CycleRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
let mut buf = [0u8; 4];
self.try_fill_bytes(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
#[inline]
fn next_u64(&mut self) -> u64 {
unimplemented!()
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
let mut buf = [0u8; 8];
self.try_fill_bytes(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
dest[..len].copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
Ok(())
}
}
// This is meant for testing only
impl CryptoRng for CycleRng {}
impl TryCryptoRng for CycleRng {}
+25 -6
View File
@@ -1,11 +1,30 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
mod cfrg_vectors;
mod mock_rng;
mod parser;
mod voprf_test_vectors;
mod voprf_vectors;
mod test_cfrg_vectors;
impl crate::CipherSuite for p256::NistP256 {
const ID: &'static [u8] = <p256::NistP256 as hash2curve::OprfParameters>::ID;
type Group = p256::NistP256;
type Hash = sha2::Sha256;
}
impl crate::CipherSuite for p384::NistP384 {
const ID: &'static [u8] = <p384::NistP384 as hash2curve::OprfParameters>::ID;
type Group = p384::NistP384;
type Hash = sha2::Sha384;
}
impl crate::CipherSuite for p521::NistP521 {
const ID: &'static [u8] = <p521::NistP521 as hash2curve::OprfParameters>::ID;
type Group = p521::NistP521;
type Hash = sha2::Sha512;
}
+14 -11
View File
@@ -1,18 +1,21 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use 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"\nA\.\d\. {2}(?P<ciphersuite>.+?)\n\n").unwrap();
let mut ciphersuites = vec![];
let chunks: Vec<&str> = re.split(input).collect();
@@ -20,7 +23,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);
@@ -31,7 +34,7 @@ fn parse_ciphersuites(input: &str) -> String {
}
fn parse_modes(input: &str) -> String {
let re = regex::Regex::new(r"### (?P<mode>.*+) Mode").unwrap();
let re = regex::Regex::new(r"A\.\d.\d\. {2}(?P<mode>.*?) Mode").unwrap();
let mut modes = vec![];
let chunks: Vec<&str> = re.split(input).collect();
@@ -39,7 +42,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);
@@ -50,7 +53,7 @@ fn parse_modes(input: &str) -> String {
}
fn parse_vectors(input: &str) -> String {
let re = regex::Regex::new(r"Test Vector.*+\n").unwrap();
let re = regex::Regex::new(r"A\.\d.\d\.\d\. {2}Test Vector.*+\n").unwrap();
let mut vectors = vec![];
let chunks: Vec<&str> = re.split(input).collect();
@@ -94,7 +97,7 @@ fn parse_params(input: &str) -> String {
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
param = format!(" \"{}\": \"{}", key, val);
param = format!(" \"{key}\": \"{val}");
} else {
let s = line.trim().to_string();
if s.contains('~') || s.contains('#') {
+527
View File
@@ -0,0 +1,527 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
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;
use p521::NistP521;
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)?;
let p521_oprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("OPRF"));
assert_ne!(p521_oprf_tvs.len(), 0);
test_oprf_seed_to_key::<NistP521>(&p521_oprf_tvs)?;
test_oprf_blind::<NistP521>(&p521_oprf_tvs)?;
test_oprf_blind_evaluate::<NistP521>(&p521_oprf_tvs)?;
test_oprf_finalize::<NistP521>(&p521_oprf_tvs)?;
test_oprf_evaluate::<NistP521>(&p521_oprf_tvs)?;
let p521_voprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("VOPRF"));
assert_ne!(p521_voprf_tvs.len(), 0);
test_voprf_seed_to_key::<NistP521>(&p521_voprf_tvs)?;
test_voprf_blind::<NistP521>(&p521_voprf_tvs)?;
test_voprf_blind_evaluate::<NistP521>(&p521_voprf_tvs)?;
test_voprf_finalize::<NistP521>(&p521_voprf_tvs)?;
test_voprf_evaluate::<NistP521>(&p521_voprf_tvs)?;
let p521_poprf_tvs =
json_to_test_vectors!(rfc, String::from("P521-SHA512"), String::from("POPRF"));
assert_ne!(p521_poprf_tvs.len(), 0);
test_poprf_seed_to_key::<NistP521>(&p521_poprf_tvs)?;
test_poprf_blind::<NistP521>(&p521_poprf_tvs)?;
test_poprf_blind_evaluate::<NistP521>(&p521_poprf_tvs)?;
test_poprf_finalize::<NistP521>(&p521_poprf_tvs)?;
test_poprf_evaluate::<NistP521>(&p521_poprf_tvs)?;
Ok(())
}
fn test_oprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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().to_vec());
}
Ok(())
}
fn test_poprf_blind_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = PoprfServer::<CS>::new_with_key(&parameters.sksm)?;
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,
)?;
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().to_vec());
}
Ok(())
}
// Tests input, blind, evaluation_element -> output
fn test_oprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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(())
}
-642
View File
@@ -1,642 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! The VOPRF test vectors taken from:
//! https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
pub(crate) static VECTORS: &str = r#"
## OPRF(ristretto255, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = caeff69352df4905a9121a4997704ca8cee1524a110819eb87deba1a39ec1
701
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf
8e03
BlindedElement = fc20e03aff3a9de9b37e8d35886ade11ec7d85c2a1fb5bb0b16
86c64e07ac467
EvaluationElement = 922e4c04b9f3b3e795d322a306c0ab9d96b667df9b949c05
2c8c75435a9dbf2f
Output = 9e857d0e8523b8eb9e995d455ae6ae19f75d85ac8b5df62c50616fb5aa0
ced3da5646698089c36dead28f9ad8e489fc0ee1c8e168725c38ed50f3783a5c520c
e
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037
e50b
BlindedElement = 483d4f39de5ff77fa0f9a0ad2334dd5bf87f2cda868539d21de
67ce49e7d1536
EvaluationElement = 6eef6ee53c6fb17c77ae47e78bdca2e1094f98785e7b9a14
f09be20797dad656
Output = b090b2ff80028771c14fecf2f37c1b14e46deec59c83d3b943c51d315bd
3bf7d32c399ed0c4ce6003339ab9ed4ad168bfb595e43530c9d73ff02ab0f1263d93
b
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = ac37d5850510299406ea8eb8fa226a7bfc2467a4b070d6c7bf667948b9600
b00
pkSm = 0c0254e22063cae3e1bae02fb6fa20882664a117c0278eda6bda3372c0dd9
860
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e326
3503
BlindedElement = 3a0a53f2c57e5ee0d89e394087f8e5f95b24159db01c31933a0
7f0e6414c954d
EvaluationElement = f8a50ed35a477b0cde91d926e1bc5ae59b97d5bd0dda51a7
28b0f036ec557d79
Proof = 7a5375eb1dbad259431f5c294e816a1c1483c279748da1a75d91f8a81438
ea08355d4087d4d848b46878dcc8fb5849ac7a09133382c2c6129564a7f7b4b7bf01
ProofRandomScalar = 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9d
bcec831b8c681a09
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
a
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd4171
ea02
BlindedElement = a86dd4544d0f3ea973926054230767dff16016215f2d73f26d3
f86a81f38cf1a
EvaluationElement = 9e47810f1de1b57ebe163a95c170ec165a2063f872155c37
6d94e8de2157af70
Proof = 61075125d851d5164b0aa1a4d5ddeebaf097266450ac6019579af5f7abd1
90088eb0f6f1e7f9d8bfddbc21ae3c25a065e6c4e797d15f345ed4fb9ee468d24c0a
ProofRandomScalar = 74ae06fd50d5f26c2519bd7b184f45dd3ef2cb50197d42df
9d013f7d6c312a0b
Output = fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f2
18047d1fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0
b
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 80513e77795feeec6d2c450589b0e1b178febd5c193a9fcba0d27f0a06e0
d50f,533c2e6d91c934f919ac218973be55ba0d7b234160a0d4cf3bddafbda99e2e0
c
BlindedElement = c24645d6378a4a86ec4682a8d86f368b1e7db870fd709a45102
492bcdc17e904,0e5ec78f839a8b6e86999bc180602690a4daae57bf5d7f827f3d40
2f56cc6c51
EvaluationElement = 3afe48eab00493eb1b073e95f57a456cde9aefe463dd1e6d
0144bf6e99ce411c,daaf9421318fd2c7fcdf369cb348748cf4dd177cce30ee4d13c
eb1644b85b653
Proof = 601381ecbe127ada04c057b8b1fc21d912f71e49252780dd0d0ac768b233
ce035f9b489a994c1d14b92d603ebcffee4f5cfadc953f69bb62648c6e662613ae00
ProofRandomScalar = 3af5aec325791592eee4a8860522f8444c8e71ac33af5186
a9706137886dce08
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
a,fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f218047d1
fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0b
~~~
## OPRF(decaf448, SHAKE-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 78f2622804104209f7e015370ff98f4a3cbf311e6784e9f4944f8a252dc08
e916d9ab1a60dc905f0e56631903ecd4ae6e15291776d61460b
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = d1080372f0fcf8c5eace50914e7127f576725f215cc7c111673c635ce668
bbbb9b50601ad89b358ab8c23ed0b6c9d040365ec9d060868714
BlindedElement = 984e0a443ee194090737df4afb402253f216b77650c91d252b6
638e1179723d51a4154b88eae396f1320f5df3c4b17f779516c456e364bd1
EvaluationElement = de477252a5ff3c7d51ce159cb8ccf1865d8c7d3402824163
8d80971f13a59d87b2b1036341b98089555ab088278391794c49bbb052fdbcff
Output = df8f910c3b84d1f3ca6afd1992768608a20f2ad7b770e9d89d303c88ba1
5bb7d991f2f7ffd5b5b51fa3bcf8fa06779609497f6c0ae4e9cb2dcd48c68b4ac6b9
4
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = aed1ffa44fd8f0ed16373606a3cf7df589cca86d8ea1abbf5768771dbef3
d401c74ae55ba1e28b9565e1e4018eb261a14134a4ce60c1c718
BlindedElement = 4aa751f84b2634b73efa364b03e60b92b84f457576e6b369eea
b76140e3859d10d2e98174f13f5a2c70670529ccf093d5f1aaf355b4f830b
EvaluationElement = 085ea1cb452a2fb15b3a0d0e1c86899c7ea49fe2e4856ef4
f95bc2542eec610fc09b0fe7d7ed7389d86af6a646695b7ad46527dc2a936aa4
Output = b57516a737879ece1110ad5d051ac0a6c54e1dcd989c907721ecebab5b4
5877cc693c3c05d0bd416c5a9ceba36de41a0a31679c146fe4c110c64b056eba1720
b
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 9eb722f7fee9f61f24ad31bc42309f73648cf4393929e8f5f333fe10c6975
c827a1eba4e03ae2fa8735db2f63f6c98c7af6010e64c81f535
pkSm = b6e2751176d57836fe1dfbdbbdc78a1b5c5a52f831226c9d8dfdf5daf8f46
6e310e80978e9b81c387f5bc85cc7ef5567f4dd3ba7674579a2
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 4c936db1779a621b6c71475ac3111fd5703a59b713929f36dfd1e892a7fe
814479c93d8b4b6e11d1f6fe5351e51457b665fa7b76074e531f
BlindedElement = d0b8e2eecad2816d45c1f8a072fe6db77d18f4b26f0889c98e2
ef856ac5df82090c1fbeac9c8e732f192b66c3b4c3f1e446ab8910c86be2f
EvaluationElement = d29849d8ad1e651328e8119003debd9ecd54cc786a5eb8ae
ea56487ffc09120e98792f9475605488d16623b8e3cfa5af1ec27e76bc841b75
Proof = 8b3b8f0c9eb22527e419f5a03d4d3f34cf725837424a38c5b4f88c7759f7
a54bade57b7930bfeff051be9bfeaabc8976ed407398e0ce462a062e068a8d57bc1c
411bc42fe714626cfb92ad854a56636c2b83f2b5215c2ff531b22e4d37031523db20
3556959e275b46b84303ed23fc37
ProofRandomScalar = 1b3f5a55b2f18f8c53d4ecf2e1c27e1028f1c345bb504486
4aa9dd8439d7520a7ba6183d50ef08bdf6c781aa465660c93e8195a8d231b62f
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
d
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 15b3355179392f40c3d5a15f0d5ffc354e340454ec779f575e4573a3886a
b5e57e4da2985cea9e32f6d95539ce2c7189e1bd7462e8c5483a
BlindedElement = 0e7ddd85c8bc5382e908241c6151afe23a41e0396759b5e38a9
affd996cd822bca242a499793555fc15f07bffdaaa93b42568b307fbdca0f
EvaluationElement = 4c81e29e8a9502fa02e00cb09cf40d9b98988ac9b4bce7cc
a0656caeb0926b59c7000d7fe6c5dd814f831864547d2360d223a50077bd04fe
Proof = 74fc8fbf2e669dc5d25898ea8ce45d1d3eb97edb4b7c3cee39865a3c66da
6b7bad4ad3e77794d6f5e82fa8a645b9b973a8612bfcd1194302f700ee3433e876d8
3f96bb70f19ff292605ad4c9466fd71dbc2ed22ade0130574e5ee343ef45d42e834a
11a19fd6f5b1b5ef910bcccf731b
ProofRandomScalar = 2f2e9955be83a4b25743ebd3618d4fad8b7288477da50bed
9befa58af639ddd950fec34205f8a4f166fadcb8fa71a3ffdd2e98f4c8ef5e26
Output = 2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c
07f59055e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f
9
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 614bb578f29cc677ea9e7aea3e4839413997e020f9377b63c13584156a09
a46dd2a425c41eac0e313a47e99d05df72c6e1d58e6592577a0d,4c115060bca87db
7d73e00cbb8559f84cb7a221b235b0950a0ab553f03f10e1386abe954011b7da62bb
6599418ef90b5d4ea98cc28aff517
BlindedElement = 5e481a4d7eaa5bab831f53f9a6311851dafd4318c6462eed4f6
15004afdb082da2f99670b0963985faac21c30eea19aacfc441412edb4c0b,8e043b
9b7afeafa07e39d9b8b88957ff07d69124b1a2b841e18c9ffb52ebf0c25144eb2501
a1d7983a44604f33a36e925eebc9bec65d9c54
EvaluationElement = 8a0d34fdb0b55121421546ff952c7bd3cbe469926ff9ad4f
aeba243823955529eeae4f1a7a64cd055ec01baa041a99dfbe1a67ca4d59f93d,5e8
6e0b41cc88186ee0003baa46535e71acd98453b298976b92be2cca2646e88620f55d
f6bf4754456dfd8d84f6889c17b5ff93052325a1a
Proof = 1ff624a102b99771c76a9414e9b3f33127897d971bc84a922e464805e4a9
f27b889922030adebbbd58e0ab618ade9c84bfe8aa226176f11f432958ea1e6f6926
3aef51db9efb23ee504d233c17e9077c0373401da167637a1df4eafd9c2537c9f89c
103f9e635931fe2042419dd9bd37
ProofRandomScalar = a614f1894bcf6a1c7cef33909b794fe6e69a642b20f4c911
8febffaf6b6a31471fe7794aa77ced123f07e56cc27de60b0ab106c0b8eab127
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
d,2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c07f5905
5e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f9
~~~
## OPRF(P-256, SHA-256)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = a1b2355828f2c76de6749af9d093bd9fe0f2cada3ec653cd9a6d3126a7a78
27b
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 5d9e7f6efd3093c32ecceabd57fb03cf760c926d2a7bfa265babf29ec98a
f0d0
BlindedElement = 03e3c379698da853d9844098fa0ac676970d5ec24167b598714
cd2ee188604ddd2
EvaluationElement = 030d8d882120e8fa67ef978a9abac506acd5ec731b8e8d6f
15035e29241dd2ced2
Output = ab653a4f3b357177b125e1c6d0bd2c0bc409b7ed5f48c99537fbd7fd11e
f8133
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 825155ab61f17605af2ae2e935c78d857c9407bcd45128d57d338f1671b5
fcbe
BlindedElement = 030b40be181ffbb3c3ae4a4911287c43261f5e4034781def69c
51608f372a02102
EvaluationElement = 03991df04e3e526d457065b6eafc855aa2fc4528c22d2b51
6a3c71227b1b488f44
Output = eca4df985f7c49b091c3ce4217be1f26cdc6a148b681ed1f1638d09dfd2
13e6e
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 4e7804245a743c59d624457677294e04a8bc4bdcd94f0d3bd54f568067489
d34
pkSm = 03b51a0af95c819b09ee80c2056cf0ab0551a5355266d3a0aaff90c3fe915
ed892
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = cee64d86fd20ab4caa264a26c0e3d42fb773b3173ba76f9588c9b14779bd
8d91
BlindedElement = 0222f5dba2da1ec7bd1086d0e04894ef1da1c11163daf376b2b
c76cc51edb16815
EvaluationElement = 02f2767135f75f69b257675b38f2bcd50338a655a5092166
3c8942ca61ea7d3c29
Proof = ffa082fc9f9a287e7edc50e3ad879ee13aebd24b69124792bdf047c643f7
0af2b50907b2fa188b90aff3b25e1d9abb02e9e2c8bfdc525c61ca008428940fca64
ProofRandomScalar = 70a5204b2b606f5a28328916e1e5ea5a17862d7a261fdd6d
959759758d5e34ac
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
94448
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 5c4b401063eff0bf242b4cd534a79bacfc2e715b2db1e7a3ad4ff8af1b24
daa2
BlindedElement = 02f84403d1ceb40a3668349f7c349f806d2c858785853324c66
7505018d13ee160
EvaluationElement = 0216d7d342ef50113244b444dfedaec78810959e40fef0a6
922658d44accb1e9c1
Proof = f496e58818c25ffb386f22ceb57a83da1200612b67aaa07608b3375c25b2
97e03e67d1f6094a8012725dc63a0c2f4f870173b97a3daa03588f777655a087fbbf
ProofRandomScalar = 3b9217801b5d51cef66d9fdbd94a53533e7c5057e09e2200
65ea8c257c0dd606
Output = 90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b9
2fe28
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = f0c7822ba317fb5e86028c44b92bd3aedcf6744d388ca013ef33edd36930
4eda,3b9631be9f8b274d9aaf671bfb6a775229bf435021b89c683259773bc686956
b
BlindedElement = 02a840214a74345570dcadfc927e726901b257b447234fac509
0a1830295ca736c,039a5a8152abb0154b4d79a90486e358ea325980f0bf590524c4
460f700454238f
EvaluationElement = 025991aac0b0c79bb1185c0b1e64964656634dfcd755cdf5
da9ee52be0b5d5f742,03319e3baba8fa7f60dab49ef0ba68b7a85bccb5d4968643e
2f029b6c0826911d1
Proof = 51b5ed453168480a2e95863cda1f4d28ad5bc91e8c9c75d788569aea1679
794a642087db120a2b3ce839f57041801f37cd4a6c05b69b327b877810293f7b09a8
ProofRandomScalar = 8306b863276ae74049615162a416d507a6532c99c1ea3f03
d05f6e78dc1edabe
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
94448,90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b92fe
28
~~~
## OPRF(P-384, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = ef1b52c12cdf43dc260bf5425a30cde7d708ec34b38dcfbdc2946d7baf525
361e797f6a98f1ebd80f64865f21cde1c6d
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 359073c015b92d15450f7fb395bf52c6ea98384c491fe4e4d423b59de7b0
df382902c13bdc9993d3717bda68fc080b99
BlindedElement = 02fa3115c21ffcacc09ca470729b725781f84333e217cfeec2b
8ba6a54ce492ede7ead3714c5b177427ef853effb1b5c24
EvaluationElement = 033a4bdea2693686e4ce467c8a5cdfc41b86ad20aaaa9bc1
6e75b59dbd41dab0bc9af0041e551ece3b4c9fb2315d8d1fa9
Output = a5a0ef3fb964a36097662d1258ef0f93b224ddd81a356c37d5dd05a885a
0b6722b90c1f5181637fece7ed180ba053da23bf35cef7a87dcba75562cb7a264001
8
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 21ece4f9b6ffd01ce82082545413bd9bb5e8f3c63b86ae88d9ce0530b01c
b1c23382c7ec9bdd6e75898e4877d8e2bc17
BlindedElement = 025fddc89a832089a59120df742acb34dba82b26afcae977961
57df238b5905c494a23c56b1f485cbbff78d31df7fa1492
EvaluationElement = 02f8b59813663e7965c219c113c560482cbea7ca4c412a0c
f3fd855ee7d543ae926d29ace85296f195f988be284b2347f6
Output = f2a0b355cae4ae2c717d0b48e39c0ee356db3ca446fddf85cddb74f397e
b85046da62d0d85d55d19d39dd9b68fcc39379ec6d3b93ba33909fcc96361d225cdd
e
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
skSm = 67ee1c9e67566d87bfcca9e5dac4bfdb8bdd727c031133fac2aa9ba6c41e6
1e5f8fd401b5d76c7d54b15b15932797479
pkSm = 029b51b2ce9c499f2056e65e0f41d60960f9c4795c0cf94af273ce840c20b
e4cdf87690b6b121b37d399b49afcc2ec9ac3
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 102f6338df84c9602bfa9e7d690b1f7a173d07e6d54a419db4a6308f8b09
589e4283efb9cd1ee4061c6bf884e60a8774
BlindedElement = 02a1f41323e91a6ac9fbbb5b8e4c7c58a4c5bcbaa4195557182
cd59e826dc847f1e077de1d402ac92eafe322461fc0d582
EvaluationElement = 03af3164f8721a57931f92884b43c58ff0ed1be249f7e1c9
3033a5909f0ffc59ed3fea9452ec5c9cfb865b8bd2e65cd209
Proof = 44108ca9b342f4d7e31a250aa9f41afb0de840e113dbb6bb82b5e6735aef
18a20867a63628be6e109d2d687e1faa8888270f1173bc6f916e21142096d23d1719
4edf844074922c287a50182f87bbb5fc3a966c8851dd6799ec5cfe59c7063c7f
ProofRandomScalar = 90f67cafc0ffaa7a1e1d1ced3c477fea691e696032c8709c
86cbcda2b184ad0029d29abeabede9788d11782429bff297
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
0
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 8aec1d0c3d16afd032da7ba961449a56cec6fb918e932b06d5778ac7f67b
ecfb3e3869237f74106241777f230582e84a
BlindedElement = 02b3465d70f76de3eaf6ecb8080490288f741c622c06d023bd1
80a55a2e3e4eaad08533651f9d278a3f59cec8277780303
EvaluationElement = 03a53e01901893585437cd48a1eea1188fc8e9275a80cf43
370a451c476dae3b84ca8c7bf44fcac2fa3eeab933b25da0c3
Proof = 5ebc467e78ae29f7d741221df0ee67285df72ec482fdc8e5bde7e588b12f
cba86f4f116c23ee6b32c0f38f2daac67e869e53e7e0494cc883e4984daf10a55819
bbb5ce7e9005f143b3dda88d8a35649269a4658a98c81c814097d15a3dcf4dbe
ProofRandomScalar = bb1876a7f7165ac7ec79bfd5213ea2e374252f29a6e19915
f81b0c7dcea93ce6580e089ede31c1b6b5b33494581b4868
Output = 5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920
267a40b246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92
c
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 41fabd4722d92472d858051ce9ad1a533176a862c697b2c392aff2aeb77e
b20c2ae6ba52fe31e13e03bf1d9f39878b23,51171628f1d28bb7402ca4aea6465e2
67b7f977a1fb71593281099ef2625644aee0b6c5f5e6e01a2b052b3bd4caf539b
BlindedElement = 02d715dfce1a0724071fa8e530d79f7b234a31739a64166e0fe
21fa6fa0fe19e1ab5e468becca899f31e365c47f3efb2ef,028dfd0c7a38b4cb8477
cae34f041344fb44fc9e55bfa3cf55ab7b4764b74accc7b49c0ff09a524598033dad
1152fb3a1c
EvaluationElement = 03f9a8c81c108201888eb86348c6f80691d99425272972b5
bf41d3038af0eeb04d60edd9ea288625a7166a8c17cea0083f,02abb31980533dbf7
eb5fee0a8969089b3e16585a2cd41a34067592a2021b1b4ea3d1cef3e7c87a6f284c
0e45546c92d98
Proof = f0f7bd2723c3460d5c5ab03092c6861fb34253470ef430dac9aeac6ce489
84b28d91178061cba02e3e911c4aa97229d519755db385ddd08064fdf8405897d1de
a472688934088505e89dcff91081fec1d2e37c1d4c5a9dddbdd358aa89f63b46
ProofRandomScalar = 1b538ff23749be19e92df82df1acd3f606cc9faa9dc7ab25
1997738a3a232f352c2059c25684e6ccea420f8d0c793fa0
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
0,5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920267a40b
246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92c
~~~
## OPRF(P-521, SHA-512)
### Base Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 016ee706f30ce7e15e4ffa3114c7d59a7b6f302d531ca60419be39d1cd43e
e13b1fc8398b7f63a900cdc49c6e99f65a74403db2fa739927a2ee288cff857d9d84
ecf
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 01b983705fcc9a39607288b935b0797ac6b3c4b2e848823ac9ae16b3a3b5
816be03432370deb7c3c17d9fc7cb4e0ce646e04e42d638e0fa7a434ed340772a8b5
d626
BlindedElement = 0301f0a8c68e58f5571bd39fe3b0b2aa055a8c34e3d68ba0d2e
d177db0bc7575d477ed8f557596feb5ac568fe738eee8cff7dcb56dc78f52bf381c0
912e0e84b5a3f5b
EvaluationElement = 0200d7b1131aa9f8c365de7bd7903738f61bdecfaada375a
ba3905bdaad1301c7cd537f69abff04140ccca29a4c46cb4a036160e55a9621210b3
71d84646b0199571fa
Output = 61eea8fedfa9338dd22fac279f1f3f9e96693919c59ea3918c7a441115e
6bdecb1d05b5da55d4024858c92d3911a81d4eca362123b2911e5dc58591bf7be29c
7
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01a03b1096b0316bc8567c89bd70267d35c8ddcb2be2cdc867089a2eb5cf
471b1e6eb4b043b9644c8539857abe3a2022e9c9fd6a1695bbabe8add48bcd149ff3
b841
BlindedElement = 030099c35342a43221c6e03debfb17bad71b62e04c9242aa6e9
f2f915163ef4f5b8b7fe1740a4d636c36bd5c73ca39c69992dc7f6dff8f232125efc
22af4df8352fea2
EvaluationElement = 0300ceeba6751486eecc479ab2259e3a57c13b0710f61c82
87acad60624974b76ea242dbcae3a9daad1bdc9c49012c8d8b384d510980cc1ef8fa
8d10502748ce63d93f
Output = 6682273a5199b2454a706cac557008e2264580ac39b6995e1f47130b985
d1015de7713d3bdb121212a68de2ece73bf72e41738a01c23428753c44e3dd39b5de
3
~~~
### Verifiable Mode
~~~
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
3a3
skSm = 0017674057e06c5e3e8a331f2dc3558540701c9cd0f4c19126d5972af6a01
447b312d05a06dab3e9e07c891d749444c27ede0897ad42aea03b887eb5db93e3f29
a86
pkSm = 0201ee4e2eaa74728f577f4bb282c5440cd454fdee1d79b15a36d34b5e5a1
25e3ccc0f99e32cc0a6a15b5652a0c8a424860c6753f685d0e1e150ceba24ca3386f
29216
~~~
#### Test Vector 1, Batch Size 1
~~~
Input = 00
Info = 7465737420696e666f
Blind = 00bbb82117c88bbd91b8954e16c0b9ceed3ce992b198be1ebfba9ba970db
d75beefbfc6d056b7f7ba1ef79f4facbf2d912c26ce2ecc5bb8d66419b379952e96b
d6f5
BlindedElement = 0200357f949a0a0bdfeb682734dbdeb778f3845045617b21436
27753332e2e75458ab183b12635c75e19afaf56981e7755803026842db1b22fa42c8
61413d07ff86545
EvaluationElement = 0201d636bac3f77c1091b337daae32259a3eacd57e3c0fb1
444fe5ce22af6acdcef4a46a2b5e169aa8d0e26ec2a3621c15dd366ba1978dae761c
1ef3dac63c60cbee88
Proof = 011ebe27ebc79e5679b643c6b3a51333499c7abee86c092181c0a8e7e539
e0ba30b1c128666708c753696ace2aa789c4975b0b80d6241a1dafe85c39a7338d1e
20d00131c8a81b5f64209f8fe53e8c6a00789a893f20596198e2521275e05d925298
08e9f54030fc8be2ce78c6df0d29e6fd7d8e623e0ccc7b19b194493dacd2a4eb3a32
ProofRandomScalar = 00ce4f0d824939827888f4c28773466f3c0a05741260040b
c9f302a4fea13f1d8f2f6b92a02a32d5eb06f81de7960470f06169bee12cf47965b7
2a59946ca3879670
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
5
~~~
#### Test Vector 2, Batch Size 1
~~~
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 009055c99bf9591cb0eab2a72d044c05ca2cc2ef9b609a38546f74b6d688
f70cf205f782fa11a0d61b2f5a8a2a1143368327f3077c68a1545e9aafbba6a90dc0
d40a
BlindedElement = 030185e431f056e75ba7fac49da70790031daa333d16f05e1de
471e24afe0ed985c770ce77bd1bebec527e9a76feecc6afd92c5fd00481ba7fb843d
2aab52337cb716e
EvaluationElement = 02000859e1abc2ed28086b854ec5ae72311244fdeedf81d7
69af6a6f2c83f00fa48df1f1a0c0b6fac84cc654b7757ac042107a6b3043e483bb3b
74de5d6c301b20e8f6
Proof = 01629dd5af14c7414801d879b1018ce06bcc5c5d0a64ca422b76aaa531c8
ecca630919fb4b51fa60fdc215f73e67e8d617d55ca6a227343d434d5e0f487567f8
5bfa016959443267bb7d9a5c5e5b1c4d20026394b4edaca7dfbc1aa3b3c2020cf995
79cf276c0e84f0cb5a820226fa3b81d42de2db39d8412642e70428e485a61ee9d760
ProofRandomScalar = 00b5dfc19eb96faba6382ec845097904db87240b9dd47b1e
487ec625f11a7ba2cc3de74c5078a81806f74dd65065273c5bd886c7f87ff8c5f39f
90320718eff747e3
Output = 7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d
1b2e936669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c13011057
1
~~~
#### Test Vector 3, Batch Size 2
~~~
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
Info = 7465737420696e666f
Blind = 01c6cf092d80c7cf2cb55388d899515238094c800bdd9c65f71780ba85f5
ae9b4703e17e559ca3ccd1944f9a70536c175f11a827452672b60d4e9f89eba28104
6e29,00cba1ba1a337759061965a423d9d3d6e1e1006dc8984ad28a4c93ecfc36fc2
171046b3c4284855cfa2434ed98db9e68a597db2c14728fade716a6a82d600444b26
e
BlindedElement = 0301978860af75cd69acbc93e8c9fc530e5d2b2208da42c65bf
e079f0f6e0b3fc6080556c10739271d2a8fe578409d4fa9b19ef0484d9c15451c4e7
0501e31da7608cb,0200e30565c3d7e02c822762f25db4c872811adb2cbfbad92b04
291bc8c476d0546d1c5ecf5c58ff06b8d19aad8eca9e5f1a80ff8e981ebc490b0cfb
d5d499b47bad8e
EvaluationElement = 0300abdee910f144c3be460e724c11626e1f9986f72e2c43
3a9c4dad2ef6fcb9249c9a5036334ba88b0892462b6f8ad419c38cc259b0c774a9bd
0c4d545d0914413ea2,02019696f91dcc178bbe6b97f822cdc4052f9b94852ff6023
f6068848f867df40e54a5f1525e7fafa383e82fe36bf3c74427b51903032d0f89876
05bf24ee003f37693
Proof = 008fa896b69c1efc4e9c6bdfd0b149444532d5ba3bfd957cf7cd71c374d3
a1cca25f17b60616164377b0734243bc878e17d3ecab36b3e3565b5c6218dae92d40
c0be018707381f6f4b0153044737030b5d9851c15609532da8932c1fa1f4901dba05
a4118d25142344f9ea1465c907eb13d908a45d8b98265eac48819a04cae859b0643a
ProofRandomScalar = 00d47b0d4ca4c64825ba085de242042b84d9ebe3b2e9de07
678ff96713dfe16f40f2c662a56ed2db95e1e7bf2dea02bd1fa76e953a630772f68b
53baade9962d1646
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
5,7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d1b2e936
669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c130110571
~~~
"#;
-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);
}
}
}
+568 -973
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