Compare commits
40
Commits
v0.1.0
...
v0.4.0-pre.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4646fe4ad0 | ||
|
|
f26f5d2c57 | ||
|
|
dfa88efeca | ||
|
|
0b3544fc79 | ||
|
|
1a61401272 | ||
|
|
a366a14125 | ||
|
|
8277383fc3 | ||
|
|
1af4f470c3 | ||
|
|
f8e0600b22 | ||
|
|
9eee936140 | ||
|
|
b59b359aa3 | ||
|
|
b01b8ed409 | ||
|
|
16e072dcd4 | ||
|
|
652fd1d1d0 | ||
|
|
e7675437e6 | ||
|
|
d316ce4c8d | ||
|
|
1c5e965446 | ||
|
|
55ef981a3f | ||
|
|
b2f6d5eac8 | ||
|
|
5228474f05 | ||
|
|
6669a0c4e6 | ||
|
|
140f9e063d | ||
|
|
4700d4b725 | ||
|
|
b1b315f23c | ||
|
|
7613610859 | ||
|
|
d9dc5d0a13 | ||
|
|
1b3837a789 | ||
|
|
093a15f597 | ||
|
|
14830f1436 | ||
|
|
8457e8b900 | ||
|
|
de91fafdb3 | ||
|
|
5ac52388ff | ||
|
|
fab7528a69 | ||
|
|
c93600498e | ||
|
|
6fb4cad59c | ||
|
|
2d8780476a | ||
|
|
a3db6cd9d2 | ||
|
|
a1ab892bdb | ||
|
|
ad04f224af | ||
|
|
7be67b26de |
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
+84
-23
@@ -7,24 +7,48 @@ on:
|
|||||||
types: [opened, repoened, synchronize]
|
types: [opened, repoened, synchronize]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
cargo-audit:
|
||||||
|
name: Audit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Cache cargo-audit
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/.crates.toml
|
||||||
|
~/.cargo/.crates2.json
|
||||||
|
~/.cargo/bin/cargo-audit
|
||||||
|
key: cargo-audit
|
||||||
|
|
||||||
|
- name: Install cargo-audit
|
||||||
|
run: cargo install cargo-audit
|
||||||
|
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Run cargo audit
|
||||||
|
run: cargo audit -D warnings
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
backend_feature:
|
backend_feature:
|
||||||
- u64_backend
|
- --features ristretto255-ciphersuite,ristretto255-u64
|
||||||
- u32_backend
|
- --features ristretto255-ciphersuite,ristretto255-u32
|
||||||
- p256,u64_backend
|
-
|
||||||
frontend_feature:
|
frontend_feature:
|
||||||
- serialize
|
-
|
||||||
|
- --features danger
|
||||||
|
- --features serde
|
||||||
toolchain:
|
toolchain:
|
||||||
- stable
|
- stable
|
||||||
- 1.51.0
|
- 1.57.0
|
||||||
name: test
|
name: test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install ${{ matrix.toolchain }} toolchain
|
- name: Install ${{ matrix.toolchain }} toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -32,19 +56,24 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: ${{ matrix.toolchain }}
|
toolchain: ${{ matrix.toolchain }}
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --no-default-features --features ${{ matrix.backend_feature }}
|
args: --no-default-features ${{ matrix.backend_feature }}
|
||||||
|
|
||||||
|
- name: Run cargo test with alloc
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --no-default-features ${{ matrix.frontend_feature }},alloc ${{ matrix.backend_feature }}
|
||||||
|
|
||||||
- name: Run cargo test with std
|
- name: Run cargo test with std
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --no-default-features --features ${{ matrix.frontend_feature }},std --features ${{ matrix.backend_feature }}
|
args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }}
|
||||||
|
|
||||||
build-no-std:
|
build-no-std:
|
||||||
name: Build with no-std on ${{ matrix.target }}
|
name: Build with no-std on ${{ matrix.target }}
|
||||||
@@ -58,16 +87,18 @@ jobs:
|
|||||||
# for any no_std target
|
# for any no_std target
|
||||||
- thumbv6m-none-eabi
|
- thumbv6m-none-eabi
|
||||||
backend_feature:
|
backend_feature:
|
||||||
- u64_backend
|
-
|
||||||
- u32_backend
|
- --features ristretto255-ciphersuite,ristretto255-u64
|
||||||
- p256,u64_backend
|
- --features ristretto255-ciphersuite,ristretto255-u32
|
||||||
frontend_feature:
|
frontend_feature:
|
||||||
- serialize
|
-
|
||||||
|
- --features danger
|
||||||
|
- --features serde
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: hecrj/setup-rust-action@v1
|
||||||
- run: rustup target add ${{ matrix.target }}
|
- run: rustup target add ${{ matrix.target }}
|
||||||
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }} --features ${{ matrix.backend_feature }}
|
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }}
|
||||||
|
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
@@ -75,7 +106,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install stable toolchain
|
- name: Install stable toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -83,32 +114,62 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: clippy
|
||||||
|
|
||||||
- name: Run cargo clippy
|
- name: Run cargo clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: -- -D warnings
|
args: --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Run cargo doc
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: -D warnings
|
||||||
|
with:
|
||||||
|
command: doc
|
||||||
|
args: --no-deps --document-private-items --features danger,std
|
||||||
|
|
||||||
|
|
||||||
format:
|
rustfmt:
|
||||||
name: cargo fmt
|
name: cargo fmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install stable toolchain
|
- name: Install nightly toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: nightly
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt
|
||||||
|
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
|
taplo:
|
||||||
|
name: Taplo
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/.crates.toml
|
||||||
|
~/.cargo/.crates2.json
|
||||||
|
~/.cargo/bin/taplo
|
||||||
|
key: taplo
|
||||||
|
|
||||||
|
- name: Install Taplo
|
||||||
|
run: cargo install taplo-cli
|
||||||
|
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Run Taplo
|
||||||
|
run: taplo fmt --check
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.0-pre.2 (April 5, 2022)
|
||||||
|
* Exposes the derive_key function under the "danger" feature
|
||||||
|
|
||||||
|
## 0.4.0-pre.1 (April 1, 2022)
|
||||||
|
* Updated to be in sync with draft-irtf-cfrg-voprf-09, with
|
||||||
|
the addition of the POPRF mode
|
||||||
|
* Added support for running the API without performing allocations
|
||||||
|
* Revamped the way the Group trait was used, so as to be more easily
|
||||||
|
extendable to other groups
|
||||||
|
* Added common traits for each public-facing struct, including serde
|
||||||
|
support
|
||||||
|
|
||||||
|
## 0.3.0 (October 25, 2021)
|
||||||
|
|
||||||
|
* Updated to be in sync with draft-irtf-cfrg-voprf-08
|
||||||
|
|
||||||
|
## 0.2.0 (October 18, 2021)
|
||||||
|
|
||||||
|
* Removed the CipherSuite interface
|
||||||
|
* Added the "danger" feature for exposing internal functions
|
||||||
|
* General improvements to the group interface
|
||||||
|
|
||||||
## 0.1.0 (September 29, 2021)
|
## 0.1.0 (September 29, 2021)
|
||||||
|
|
||||||
* Initial release
|
* Initial release
|
||||||
|
|||||||
+45
-39
@@ -1,56 +1,62 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "voprf"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
|
|
||||||
authors = ["Kevin Lewi <[email protected]>"]
|
authors = ["Kevin Lewi <[email protected]>"]
|
||||||
categories = ["no-std"]
|
categories = ["no-std", "algorithms", "cryptography"]
|
||||||
|
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
|
||||||
|
edition = "2021"
|
||||||
|
keywords = ["oprf"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2018"
|
name = "voprf"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
resolver = "2"
|
repository = "https://github.com/novifinancial/voprf/"
|
||||||
|
rust-version = "1.57"
|
||||||
|
version = "0.4.0-pre.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["u64_backend", "serialize"]
|
alloc = []
|
||||||
p256 = ["num-bigint", "num-integer", "num-traits", "once_cell", "p256_"]
|
danger = []
|
||||||
std = ["curve25519-dalek/std", "getrandom", "rand/std", "rand/std_rng", "num-bigint/std", "num-integer/std", "num-traits/std"]
|
default = ["ristretto255-ciphersuite", "ristretto255-u64", "serde"]
|
||||||
u64_backend = ["curve25519-dalek/u64_backend"]
|
ristretto255 = ["curve25519-dalek", "generic-array/more_lengths"]
|
||||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
ristretto255-ciphersuite = ["ristretto255", "sha2"]
|
||||||
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
|
ristretto255-fiat-u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"]
|
||||||
|
ristretto255-fiat-u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"]
|
||||||
|
ristretto255-simd = ["curve25519-dalek/simd_backend", "ristretto255"]
|
||||||
|
ristretto255-u32 = ["curve25519-dalek/u32_backend", "ristretto255"]
|
||||||
|
ristretto255-u64 = ["curve25519-dalek/u64_backend", "ristretto255"]
|
||||||
|
serde = ["generic-array/serde", "serde_"]
|
||||||
|
std = ["alloc"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
|
curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true }
|
||||||
constant_time_eq = "0.1"
|
derive-where = { version = "=1.0.0-rc.3", features = ["zeroize-on-drop"] }
|
||||||
curve25519-dalek = { version = "3", default-features = false }
|
digest = "0.10"
|
||||||
digest = "0.9"
|
|
||||||
displaydoc = { version = "0.2", default-features = false }
|
displaydoc = { version = "0.2", default-features = false }
|
||||||
|
elliptic-curve = { version = "=0.12.0-pre.1", features = [
|
||||||
|
"hash2curve",
|
||||||
|
"sec1",
|
||||||
|
"voprf",
|
||||||
|
] }
|
||||||
generic-array = "0.14"
|
generic-array = "0.14"
|
||||||
getrandom = { version = "0.2", optional = true }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
hkdf = "0.11"
|
serde_ = { version = "1", package = "serde", default-features = false, features = [
|
||||||
hmac = "0.11"
|
"derive",
|
||||||
num-bigint = { version = "0.4", default-features = false, optional = true }
|
], optional = true }
|
||||||
num-integer = { version = "0.1", default-features = false, optional = true }
|
sha2 = { version = "0.10", default-features = false, optional = true }
|
||||||
num-traits = { version = "0.2", default-features = false, optional = true }
|
|
||||||
once_cell = { version = "1", default-features = false, optional = true }
|
|
||||||
p256_ = { package = "p256", version = "0.9", default-features = false, features = ["arithmetic", "zeroize"], optional = true }
|
|
||||||
rand = { version = "0.8", default-features = false }
|
|
||||||
serde = { version = "1", default-features = false, features = ["alloc", "derive"], optional = true }
|
|
||||||
subtle = { version = "2.3", default-features = false }
|
subtle = { version = "2.3", default-features = false }
|
||||||
zeroize = { version = "1", features = ["zeroize_derive"] }
|
zeroize = { version = "1.5", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
||||||
getrandom = { version = "0.2", features = ["js"], optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64 = "0.13"
|
generic-array = { version = "0.14", features = ["more_lengths"] }
|
||||||
bincode = "1"
|
|
||||||
chacha20poly1305 = "0.8"
|
|
||||||
criterion = "0.3"
|
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
json = "0.12"
|
json = "0.12"
|
||||||
lazy_static = "1"
|
p256 = { version = "=0.11.0-pre.0", default-features = false, features = [
|
||||||
serde_json = "1"
|
"hash2curve",
|
||||||
sha2 = "0.9"
|
"voprf",
|
||||||
|
] }
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
|
rand = "0.8"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
rustyline = "8"
|
sha2 = "0.10"
|
||||||
voprf = { path = "", default-features = false, features = ["std"] }
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["danger", "std"]
|
||||||
|
targets = []
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ Installation
|
|||||||
Add the following line to the dependencies of your `Cargo.toml`:
|
Add the following line to the dependencies of your `Cargo.toml`:
|
||||||
|
|
||||||
```
|
```
|
||||||
voprf = "0.1.0"
|
voprf = "0.4.0-pre.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Minimum Supported Rust Version
|
### Minimum Supported Rust Version
|
||||||
|
|
||||||
Rust **1.51** or higher.
|
Rust **1.57** or higher.
|
||||||
|
|
||||||
Contributors
|
Contributors
|
||||||
------------
|
------------
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_strings = true
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
imports_granularity = "Module"
|
||||||
|
license_template_path = ".cargo/license.rs"
|
||||||
|
newline_style = "Unix"
|
||||||
|
unstable_features = true
|
||||||
|
wrap_comments = true
|
||||||
+36
-5
@@ -7,11 +7,42 @@
|
|||||||
|
|
||||||
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
|
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
|
||||||
|
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::{Digest, OutputSizeUser};
|
||||||
|
use elliptic_curve::VoprfParameters;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||||
|
|
||||||
|
use crate::Group;
|
||||||
|
|
||||||
/// Configures the underlying primitives used in VOPRF
|
/// Configures the underlying primitives used in VOPRF
|
||||||
pub trait CipherSuite {
|
pub trait CipherSuite
|
||||||
|
where
|
||||||
|
<Self::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// The ciphersuite identifier as dictated by
|
||||||
|
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/>
|
||||||
|
const ID: u16;
|
||||||
|
|
||||||
/// A finite cyclic group along with a point representation that allows some
|
/// A finite cyclic group along with a point representation that allows some
|
||||||
/// customization on how to hash an input to a curve point. See `group::Group`.
|
/// customization on how to hash an input to a curve point. See [`Group`].
|
||||||
type Group: crate::group::Group;
|
type Group: Group;
|
||||||
/// The main hash function to use (for HKDF computations and hashing transcripts).
|
|
||||||
type Hash: crate::hash::Hash;
|
/// The main hash function to use (for HKDF computations and hashing
|
||||||
|
/// transcripts).
|
||||||
|
type Hash: BlockSizeUser + Digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VoprfParameters> CipherSuite for T
|
||||||
|
where
|
||||||
|
T: Group,
|
||||||
|
T::Hash: BlockSizeUser + Digest,
|
||||||
|
<T::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
const ID: u16 = T::ID;
|
||||||
|
|
||||||
|
type Group = T;
|
||||||
|
|
||||||
|
type Hash = T::Hash;
|
||||||
}
|
}
|
||||||
|
|||||||
+473
@@ -0,0 +1,473 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
//! Common functionality between multiple OPRF modes.
|
||||||
|
|
||||||
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
|
use derive_where::derive_where;
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::{Digest, OutputSizeUser};
|
||||||
|
use generic_array::sequence::Concat;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U11, U2, U256};
|
||||||
|
use generic_array::{ArrayLength, GenericArray};
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use crate::serialization::serde::{Element, Scalar};
|
||||||
|
use crate::{CipherSuite, Error, Group, InternalError, Result};
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Constants //
|
||||||
|
// ========= //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
pub(crate) const STR_FINALIZE: [u8; 8] = *b"Finalize";
|
||||||
|
pub(crate) const STR_SEED: [u8; 5] = *b"Seed-";
|
||||||
|
pub(crate) const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair";
|
||||||
|
pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite";
|
||||||
|
pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge";
|
||||||
|
pub(crate) const STR_INFO: [u8; 4] = *b"Info";
|
||||||
|
pub(crate) const STR_VOPRF: [u8; 8] = *b"VOPRF09-";
|
||||||
|
pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-";
|
||||||
|
pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-";
|
||||||
|
|
||||||
|
/// Determines the mode of operation (either base mode or verifiable mode). This
|
||||||
|
/// is only used for custom implementations for [`Group`].
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Mode {
|
||||||
|
/// Non-verifiable mode.
|
||||||
|
Oprf,
|
||||||
|
/// Verifiable mode.
|
||||||
|
Voprf,
|
||||||
|
/// Partially-oblivious mode.
|
||||||
|
Poprf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
/// Mode as it is represented in a context string.
|
||||||
|
pub fn to_u8(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Mode::Oprf => 0,
|
||||||
|
Mode::Voprf => 1,
|
||||||
|
Mode::Poprf => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// High-level API Structs //
|
||||||
|
// ====================== //
|
||||||
|
////////////////////////////
|
||||||
|
|
||||||
|
/// The first client message sent from a client (either verifiable or not) to a
|
||||||
|
/// server (either verifiable or not).
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct BlindedElement<CS: CipherSuite>(
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
|
||||||
|
pub(crate) <CS::Group as Group>::Elem,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
|
||||||
|
|
||||||
|
/// The server's response to the [BlindedElement] message from a client (either
|
||||||
|
/// verifiable or not) to a server (either verifiable or not).
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct EvaluationElement<CS: CipherSuite>(
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
|
||||||
|
pub(crate) <CS::Group as Group>::Elem,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
|
||||||
|
|
||||||
|
/// Contains prepared [`EvaluationElement`]s by a server batch evaluate
|
||||||
|
/// preparation.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>)
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
|
||||||
|
|
||||||
|
/// A proof produced by a server that the OPRF output matches against a server
|
||||||
|
/// public key.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct Proof<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) c_scalar: <CS::Group as Group>::Scalar,
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) s_scalar: <CS::Group as Group>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// Proof Functions //
|
||||||
|
// =============== //
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Batch`].
|
||||||
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
pub(crate) fn generate_proof<CS: CipherSuite, R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
k: <CS::Group as Group>::Scalar,
|
||||||
|
a: <CS::Group as Group>::Elem,
|
||||||
|
b: <CS::Group as Group>::Elem,
|
||||||
|
cs: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
ds: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<Proof<CS>>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.1
|
||||||
|
|
||||||
|
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
|
||||||
|
|
||||||
|
let r = CS::Group::random_scalar(rng);
|
||||||
|
let t2 = a * &r;
|
||||||
|
let t3 = m * &r;
|
||||||
|
|
||||||
|
// Bm = GG.SerializeElement(B)
|
||||||
|
let bm = CS::Group::serialize_elem(b);
|
||||||
|
// a0 = GG.SerializeElement(M)
|
||||||
|
let a0 = CS::Group::serialize_elem(m);
|
||||||
|
// a1 = GG.SerializeElement(Z)
|
||||||
|
let a1 = CS::Group::serialize_elem(z);
|
||||||
|
// a2 = GG.SerializeElement(t2)
|
||||||
|
let a2 = CS::Group::serialize_elem(t2);
|
||||||
|
// a3 = GG.SerializeElement(t3)
|
||||||
|
let a3 = CS::Group::serialize_elem(t3);
|
||||||
|
|
||||||
|
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
|
||||||
|
|
||||||
|
// h2Input = I2OSP(len(Bm), 2) || Bm ||
|
||||||
|
// I2OSP(len(a0), 2) || a0 ||
|
||||||
|
// I2OSP(len(a1), 2) || a1 ||
|
||||||
|
// I2OSP(len(a2), 2) || a2 ||
|
||||||
|
// I2OSP(len(a3), 2) || a3 ||
|
||||||
|
// "Challenge"
|
||||||
|
let h2_input = [
|
||||||
|
&elem_len,
|
||||||
|
bm.as_slice(),
|
||||||
|
&elem_len,
|
||||||
|
&a0,
|
||||||
|
&elem_len,
|
||||||
|
&a1,
|
||||||
|
&elem_len,
|
||||||
|
&a2,
|
||||||
|
&elem_len,
|
||||||
|
&a3,
|
||||||
|
&STR_CHALLENGE,
|
||||||
|
];
|
||||||
|
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode));
|
||||||
|
// This can't fail, the size of the `input` is known.
|
||||||
|
let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap();
|
||||||
|
let s_scalar = r - &(c_scalar * &k);
|
||||||
|
|
||||||
|
Ok(Proof { c_scalar, s_scalar })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::ProofVerification`] or [`Error::Batch`].
|
||||||
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
pub(crate) fn verify_proof<CS: CipherSuite>(
|
||||||
|
a: <CS::Group as Group>::Elem,
|
||||||
|
b: <CS::Group as Group>::Elem,
|
||||||
|
cs: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
ds: impl Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
proof: &Proof<CS>,
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.2
|
||||||
|
let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?;
|
||||||
|
let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar);
|
||||||
|
let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar);
|
||||||
|
|
||||||
|
// Bm = GG.SerializeElement(B)
|
||||||
|
let bm = CS::Group::serialize_elem(b);
|
||||||
|
// a0 = GG.SerializeElement(M)
|
||||||
|
let a0 = CS::Group::serialize_elem(m);
|
||||||
|
// a1 = GG.SerializeElement(Z)
|
||||||
|
let a1 = CS::Group::serialize_elem(z);
|
||||||
|
// a2 = GG.SerializeElement(t2)
|
||||||
|
let a2 = CS::Group::serialize_elem(t2);
|
||||||
|
// a3 = GG.SerializeElement(t3)
|
||||||
|
let a3 = CS::Group::serialize_elem(t3);
|
||||||
|
|
||||||
|
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
|
||||||
|
|
||||||
|
// h2Input = I2OSP(len(Bm), 2) || Bm ||
|
||||||
|
// I2OSP(len(a0), 2) || a0 ||
|
||||||
|
// I2OSP(len(a1), 2) || a1 ||
|
||||||
|
// I2OSP(len(a2), 2) || a2 ||
|
||||||
|
// I2OSP(len(a3), 2) || a3 ||
|
||||||
|
// "Challenge"
|
||||||
|
let h2_input = [
|
||||||
|
&elem_len,
|
||||||
|
bm.as_slice(),
|
||||||
|
&elem_len,
|
||||||
|
&a0,
|
||||||
|
&elem_len,
|
||||||
|
&a1,
|
||||||
|
&elem_len,
|
||||||
|
&a2,
|
||||||
|
&elem_len,
|
||||||
|
&a3,
|
||||||
|
&STR_CHALLENGE,
|
||||||
|
];
|
||||||
|
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode));
|
||||||
|
// This can't fail, the size of the `input` is known.
|
||||||
|
let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap();
|
||||||
|
|
||||||
|
match c.ct_eq(&proof.c_scalar).into() {
|
||||||
|
true => Ok(()),
|
||||||
|
false => Err(Error::ProofVerification),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComputeCompositesResult<CS> = (
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Elem,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Elem,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Batch`].
|
||||||
|
fn compute_composites<
|
||||||
|
CS: CipherSuite,
|
||||||
|
IC: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
ID: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
>(
|
||||||
|
k_option: Option<<CS::Group as Group>::Scalar>,
|
||||||
|
b: <CS::Group as Group>::Elem,
|
||||||
|
c_slice: IC,
|
||||||
|
d_slice: ID,
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<ComputeCompositesResult<CS>>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#section-2.2.1
|
||||||
|
|
||||||
|
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
|
||||||
|
|
||||||
|
if c_slice.len() != d_slice.len() {
|
||||||
|
return Err(Error::Batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?;
|
||||||
|
|
||||||
|
// seedDST = "Seed-" || contextString
|
||||||
|
let seed_dst = GenericArray::from(STR_SEED).concat(create_context_string::<CS>(mode));
|
||||||
|
|
||||||
|
// h1Input = I2OSP(len(Bm), 2) || Bm ||
|
||||||
|
// I2OSP(len(seedDST), 2) || seedDST
|
||||||
|
// seed = Hash(h1Input)
|
||||||
|
let seed = CS::Hash::new()
|
||||||
|
.chain_update(&elem_len)
|
||||||
|
.chain_update(CS::Group::serialize_elem(b))
|
||||||
|
.chain_update(i2osp_2_array(&seed_dst))
|
||||||
|
.chain_update(seed_dst)
|
||||||
|
.finalize();
|
||||||
|
let seed_len = i2osp_2_array(&seed);
|
||||||
|
|
||||||
|
let mut m = CS::Group::identity_elem();
|
||||||
|
let mut z = CS::Group::identity_elem();
|
||||||
|
|
||||||
|
for (i, (c, d)) in (0..len).zip(c_slice.zip(d_slice)) {
|
||||||
|
// Ci = GG.SerializeElement(Cs[i])
|
||||||
|
let ci = CS::Group::serialize_elem(c);
|
||||||
|
// Di = GG.SerializeElement(Ds[i])
|
||||||
|
let di = CS::Group::serialize_elem(d);
|
||||||
|
// h2Input = I2OSP(len(seed), 2) || seed || I2OSP(i, 2) ||
|
||||||
|
// I2OSP(len(Ci), 2) || Ci ||
|
||||||
|
// I2OSP(len(Di), 2) || Di ||
|
||||||
|
// "Composite"
|
||||||
|
let h2_input = [
|
||||||
|
seed_len.as_slice(),
|
||||||
|
&seed,
|
||||||
|
&i.to_be_bytes(),
|
||||||
|
&elem_len,
|
||||||
|
&ci,
|
||||||
|
&elem_len,
|
||||||
|
&di,
|
||||||
|
&STR_COMPOSITE,
|
||||||
|
];
|
||||||
|
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(mode));
|
||||||
|
// This can't fail, the size of the `input` is known.
|
||||||
|
let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst).unwrap();
|
||||||
|
m = c * &di + &m;
|
||||||
|
z = match k_option {
|
||||||
|
Some(_) => z,
|
||||||
|
None => d * &di + &z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
z = match k_option {
|
||||||
|
Some(k) => m * &k,
|
||||||
|
None => z,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((m, z))
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// Inner Functions //
|
||||||
|
// =============== //
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
|
||||||
|
pub(crate) fn derive_key_internal<CS: CipherSuite>(
|
||||||
|
seed: &[u8],
|
||||||
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<<CS::Group as Group>::Scalar, Error>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let context_string = create_context_string::<CS>(mode);
|
||||||
|
let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string);
|
||||||
|
|
||||||
|
let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;
|
||||||
|
|
||||||
|
for counter in 0_u8..=u8::MAX {
|
||||||
|
// deriveInput = seed || I2OSP(len(info), 2) || info
|
||||||
|
// skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair"
|
||||||
|
// || contextString)
|
||||||
|
let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
|
||||||
|
&[seed, &info_len, info, &counter.to_be_bytes()],
|
||||||
|
&dst,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::DeriveKeyPair)?;
|
||||||
|
|
||||||
|
if !bool::from(CS::Group::is_zero_scalar(sk_s)) {
|
||||||
|
return Ok(sk_s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn derive_key<CS: CipherSuite>(
|
||||||
|
seed: &[u8],
|
||||||
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<<CS::Group as Group>::Scalar, Error>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
derive_key_internal::<CS>(seed, info, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeriveKeypairResult<CS> = (
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Scalar,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Elem,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`].
|
||||||
|
pub(crate) fn derive_keypair<CS: CipherSuite>(
|
||||||
|
seed: &[u8],
|
||||||
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<DeriveKeypairResult<CS>, Error>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let sk_s = derive_key_internal::<CS>(seed, info, mode)?;
|
||||||
|
let pk_s = CS::Group::base_elem() * &sk_s;
|
||||||
|
|
||||||
|
Ok((sk_s, pk_s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner function for blind that assumes that the blinding factor has already
|
||||||
|
/// been chosen, and therefore takes it as input. Does not check if the blinding
|
||||||
|
/// factor is non-zero.
|
||||||
|
///
|
||||||
|
/// Can only fail with [`Error::Input`].
|
||||||
|
pub(crate) fn deterministic_blind_unchecked<CS: CipherSuite>(
|
||||||
|
input: &[u8],
|
||||||
|
blind: &<CS::Group as Group>::Scalar,
|
||||||
|
mode: Mode,
|
||||||
|
) -> Result<<CS::Group as Group>::Elem>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
|
||||||
|
let hashed_point =
|
||||||
|
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).map_err(|_| Error::Input)?;
|
||||||
|
Ok(hashed_point * blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the contextString parameter as defined in
|
||||||
|
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/>
|
||||||
|
pub(crate) fn create_context_string<CS: CipherSuite>(mode: Mode) -> GenericArray<u8, U11>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
GenericArray::from(STR_VOPRF)
|
||||||
|
.concat([mode.to_u8()].into())
|
||||||
|
.concat(CS::ID.to_be_bytes().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Utility Functions //
|
||||||
|
// ================= //
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
|
||||||
|
u16::try_from(input)
|
||||||
|
.map(|input| input.to_be_bytes())
|
||||||
|
.map_err(|_| InternalError::I2osp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn i2osp_2_array<L: ArrayLength<u8> + IsLess<U256>>(
|
||||||
|
_: &GenericArray<u8, L>,
|
||||||
|
) -> GenericArray<u8, U2> {
|
||||||
|
L::U16.to_be_bytes().into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
//! Errors which are produced during an execution of the protocol
|
||||||
|
|
||||||
|
use displaydoc::Display;
|
||||||
|
|
||||||
|
/// [`Result`](core::result::Result) shorthand that uses [`Error`].
|
||||||
|
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// Represents an error in the manipulation of internal cryptographic data
|
||||||
|
#[derive(Clone, Copy, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Size of info is longer then [`u16::MAX`].
|
||||||
|
Info,
|
||||||
|
/// Size of input is empty or longer then [`u16::MAX`].
|
||||||
|
Input,
|
||||||
|
/// Size of info and seed together are longer then `u16::MAX - 3`.
|
||||||
|
DeriveKeyPair,
|
||||||
|
/// Failure to deserialize bytes
|
||||||
|
Deserialization,
|
||||||
|
/// Batched items are more then [`u16::MAX`] or length don't match.
|
||||||
|
Batch,
|
||||||
|
/// In verifiable mode, occurs when the proof failed to verify
|
||||||
|
ProofVerification,
|
||||||
|
/// The protocol has failed and can't be completed.
|
||||||
|
Protocol,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only used to implement [`Group`](crate::Group).
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub enum InternalError {
|
||||||
|
/// Size of input is empty or longer then [`u16::MAX`].
|
||||||
|
Input,
|
||||||
|
/// `input` is longer then [`u16::MAX`].
|
||||||
|
I2osp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for Error {}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
//! A list of error types which are produced during an execution of the protocol
|
|
||||||
use core::fmt::Debug;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use displaydoc::Display;
|
|
||||||
|
|
||||||
/// Represents an error in the manipulation of internal cryptographic data
|
|
||||||
#[derive(Clone, Display, Eq, Hash, PartialEq)]
|
|
||||||
pub enum InternalError {
|
|
||||||
/// Could not parse byte sequence for key
|
|
||||||
InvalidByteSequence,
|
|
||||||
/// Could not deserialize element, or deserialized to the identity element
|
|
||||||
PointError,
|
|
||||||
/// Computing the hash-to-curve function failed
|
|
||||||
HashToCurveError,
|
|
||||||
/// Failure to serialize or deserialize bytes
|
|
||||||
SerializationError,
|
|
||||||
/// Use of incompatible modes (base vs. verifiable)
|
|
||||||
IncompatibleModeError,
|
|
||||||
/**
|
|
||||||
* Internal error thrown when different-lengthed slices are supplied
|
|
||||||
* to the compute_composites() function.
|
|
||||||
*/
|
|
||||||
MismatchedLengthsForCompositeInputs,
|
|
||||||
/// In verifiable mode, occurs when the proof failed to verify
|
|
||||||
ProofVerificationError,
|
|
||||||
/// Encountered insufficient bytes when attempting to deserialize
|
|
||||||
SizeError,
|
|
||||||
/// Encountered a zero scalar
|
|
||||||
ZeroScalarError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for InternalError {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::InvalidByteSequence => f.debug_tuple("InvalidByteSequence").finish(),
|
|
||||||
Self::PointError => f.debug_tuple("PointError").finish(),
|
|
||||||
Self::HashToCurveError => f.debug_tuple("HashToCurveError").finish(),
|
|
||||||
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
|
|
||||||
Self::IncompatibleModeError => f.debug_tuple("IncompatibleModeError").finish(),
|
|
||||||
Self::MismatchedLengthsForCompositeInputs => f
|
|
||||||
.debug_tuple("MismatchedLengthsForCompositeInputs")
|
|
||||||
.finish(),
|
|
||||||
Self::ProofVerificationError => f.debug_tuple("ProofVerificationError").finish(),
|
|
||||||
Self::SizeError => f.debug_tuple("SizeError").finish(),
|
|
||||||
Self::ZeroScalarError => f.debug_tuple("ZeroScalarError").finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl Error for InternalError {}
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::Digest;
|
||||||
|
use elliptic_curve::group::cofactor::CofactorGroup;
|
||||||
|
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
|
||||||
|
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
|
||||||
|
use elliptic_curve::{
|
||||||
|
AffinePoint, Field, FieldSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey,
|
||||||
|
};
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use super::Group;
|
||||||
|
use crate::{Error, InternalError, Result};
|
||||||
|
|
||||||
|
impl<C> Group for C
|
||||||
|
where
|
||||||
|
C: GroupDigest,
|
||||||
|
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
|
||||||
|
FieldSize<Self>: ModulusSize,
|
||||||
|
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
|
||||||
|
Scalar<Self>: FromOkm,
|
||||||
|
{
|
||||||
|
type Elem = ProjectivePoint<Self>;
|
||||||
|
|
||||||
|
type ElemLen = <FieldSize<Self> as ModulusSize>::CompressedPointSize;
|
||||||
|
|
||||||
|
type Scalar = Scalar<Self>;
|
||||||
|
|
||||||
|
type ScalarLen = FieldSize<Self>;
|
||||||
|
|
||||||
|
// Implements the `hash_to_curve()` function from
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||||
|
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
|
||||||
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||||
|
{
|
||||||
|
Self::hash_from_bytes::<ExpandMsgXmd<H>>(input, dst).map_err(|_| InternalError::Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the `HashToScalar()` function
|
||||||
|
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError>
|
||||||
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||||
|
{
|
||||||
|
<Self as GroupDigest>::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
|
||||||
|
.map_err(|_| InternalError::Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_elem() -> Self::Elem {
|
||||||
|
ProjectivePoint::<Self>::generator()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identity_elem() -> Self::Elem {
|
||||||
|
ProjectivePoint::<Self>::identity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
|
||||||
|
let bytes = elem.to_encoded_point(true);
|
||||||
|
let bytes = bytes.as_bytes();
|
||||||
|
let mut result = GenericArray::default();
|
||||||
|
result[..bytes.len()].copy_from_slice(bytes);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
|
||||||
|
PublicKey::<Self>::from_sec1_bytes(element_bits)
|
||||||
|
.map(|public_key| public_key.to_projective())
|
||||||
|
.map_err(|_| Error::Deserialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||||
|
*SecretKey::<Self>::random(rng).to_nonzero_scalar()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
|
||||||
|
Option::from(scalar.invert()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
|
||||||
|
scalar.is_zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn zero_scalar() -> Self::Scalar {
|
||||||
|
Scalar::<Self>::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
||||||
|
scalar.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
|
||||||
|
SecretKey::<Self>::from_be_bytes(scalar_bits)
|
||||||
|
.map(|secret_key| *secret_key.to_nonzero_scalar())
|
||||||
|
.map_err(|_| Error::Deserialization)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
use crate::errors::InternalError;
|
|
||||||
use crate::hash::Hash;
|
|
||||||
use crate::serialization::i2osp;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use digest::{BlockInput, Digest};
|
|
||||||
use generic_array::typenum::Unsigned;
|
|
||||||
|
|
||||||
// Computes ceil(x / y)
|
|
||||||
fn div_ceil(x: usize, y: usize) -> usize {
|
|
||||||
let additive = (x % y != 0) as usize;
|
|
||||||
x / y + additive
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalError> {
|
|
||||||
if x.len() != y.len() {
|
|
||||||
return Err(InternalError::HashToCurveError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to the expand_message_xmd() function defined in
|
|
||||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
|
|
||||||
pub fn expand_message_xmd<H: Hash>(
|
|
||||||
msg: &[u8],
|
|
||||||
dst: &[u8],
|
|
||||||
len_in_bytes: usize,
|
|
||||||
) -> Result<Vec<u8>, InternalError> {
|
|
||||||
let b_in_bytes = <H as Digest>::OutputSize::USIZE;
|
|
||||||
let r_in_bytes = <H as BlockInput>::BlockSize::USIZE;
|
|
||||||
|
|
||||||
let ell = div_ceil(len_in_bytes, b_in_bytes);
|
|
||||||
if ell > 255 {
|
|
||||||
return Err(InternalError::HashToCurveError);
|
|
||||||
}
|
|
||||||
let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat();
|
|
||||||
let z_pad = i2osp(0, r_in_bytes)?;
|
|
||||||
let l_i_b_str = i2osp(len_in_bytes, 2)?;
|
|
||||||
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat();
|
|
||||||
|
|
||||||
let mut b: Vec<Vec<u8>> = alloc::vec![H::digest(&msg_prime).to_vec()]; // b[0]
|
|
||||||
|
|
||||||
let mut h = H::new();
|
|
||||||
h.update(&b[0]);
|
|
||||||
h.update(&i2osp(1, 1)?);
|
|
||||||
h.update(&dst_prime);
|
|
||||||
b.push(h.finalize_reset().to_vec()); // b[1]
|
|
||||||
|
|
||||||
let mut uniform_bytes: Vec<u8> = Vec::new();
|
|
||||||
uniform_bytes.extend_from_slice(&b[1]);
|
|
||||||
|
|
||||||
for i in 2..(ell + 1) {
|
|
||||||
h.update(xor(&b[0], &b[i - 1])?);
|
|
||||||
h.update(&i2osp(i, 1)?);
|
|
||||||
h.update(&dst_prime);
|
|
||||||
b.push(h.finalize_reset().to_vec()); // b[i]
|
|
||||||
uniform_bytes.extend_from_slice(&b[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(uniform_bytes[..len_in_bytes].to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
struct Params {
|
|
||||||
msg: &'static str,
|
|
||||||
len_in_bytes: usize,
|
|
||||||
uniform_bytes: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_expand_message_xmd() {
|
|
||||||
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
|
||||||
let test_vectors: alloc::vec::Vec<Params> = alloc::vec![
|
|
||||||
Params {
|
|
||||||
msg: "",
|
|
||||||
len_in_bytes: 0x20,
|
|
||||||
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
|
|
||||||
92181df928fca88",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abc",
|
|
||||||
len_in_bytes: 0x20,
|
|
||||||
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
|
|
||||||
5a1151775fe4da7",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abcdef0123456789",
|
|
||||||
len_in_bytes: 0x20,
|
|
||||||
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
||||||
len_in_bytes: 0x20,
|
|
||||||
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
|
|
||||||
1716b1b964e1c642",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
len_in_bytes: 0x20,
|
|
||||||
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
|
|
||||||
350db46f429b771b",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "",
|
|
||||||
len_in_bytes: 0x80,
|
|
||||||
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
|
|
||||||
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
|
|
||||||
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
|
|
||||||
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
|
|
||||||
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abc",
|
|
||||||
len_in_bytes: 0x80,
|
|
||||||
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
|
|
||||||
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
|
|
||||||
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
|
|
||||||
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
|
|
||||||
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abcdef0123456789",
|
|
||||||
len_in_bytes: 0x80,
|
|
||||||
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
|
|
||||||
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
|
|
||||||
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
|
|
||||||
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
|
|
||||||
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
||||||
len_in_bytes: 0x80,
|
|
||||||
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
|
|
||||||
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
|
|
||||||
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
|
|
||||||
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
|
|
||||||
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
len_in_bytes: 0x80,
|
|
||||||
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
|
|
||||||
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
|
|
||||||
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
|
|
||||||
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
|
|
||||||
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let dst = "QUUX-V01-CS02-with-expander";
|
|
||||||
|
|
||||||
for tv in test_vectors {
|
|
||||||
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
|
|
||||||
tv.msg.as_bytes(),
|
|
||||||
dst.as_bytes(),
|
|
||||||
tv.len_in_bytes,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+81
-90
@@ -7,122 +7,113 @@
|
|||||||
|
|
||||||
//! Defines the Group trait to specify the underlying prime order group
|
//! Defines the Group trait to specify the underlying prime order group
|
||||||
|
|
||||||
mod expand;
|
mod elliptic_curve;
|
||||||
#[cfg(feature = "p256")]
|
#[cfg(feature = "ristretto255")]
|
||||||
pub(crate) mod p256;
|
|
||||||
mod ristretto;
|
mod ristretto;
|
||||||
|
|
||||||
use crate::errors::InternalError;
|
|
||||||
use crate::hash::Hash;
|
|
||||||
use core::ops::{Add, Mul, Sub};
|
use core::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::Digest;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||||
use generic_array::{ArrayLength, GenericArray};
|
use generic_array::{ArrayLength, GenericArray};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
pub use ristretto::Ristretto255;
|
||||||
|
use subtle::{Choice, ConstantTimeEq};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{InternalError, Result};
|
||||||
|
|
||||||
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
|
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
|
||||||
/// subgroup is noted additively — as in the draft RFC — in this trait.
|
/// subgroup is noted additively — as in the draft RFC — in this trait.
|
||||||
pub trait Group:
|
pub trait Group {
|
||||||
Copy
|
/// The type of group elements
|
||||||
+ Sized
|
type Elem: ConstantTimeEq
|
||||||
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
|
|
||||||
+ for<'a> Add<&'a Self, Output = Self>
|
|
||||||
{
|
|
||||||
/// The ciphersuite identifier as dictated by
|
|
||||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
|
|
||||||
const SUITE_ID: usize;
|
|
||||||
|
|
||||||
/// transforms a password and domain separation tag (DST) into a curve point
|
|
||||||
fn hash_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError>;
|
|
||||||
|
|
||||||
/// Hashes a slice of pseudo-random bytes to a scalar
|
|
||||||
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError>;
|
|
||||||
|
|
||||||
/// The type of base field scalars
|
|
||||||
type Scalar: Zeroize
|
|
||||||
+ Copy
|
+ Copy
|
||||||
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
|
+ Zeroize
|
||||||
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
|
+ for<'a> Add<&'a Self::Elem, Output = Self::Elem>
|
||||||
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
|
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>;
|
||||||
/// The byte length necessary to represent scalars
|
|
||||||
type ScalarLen: ArrayLength<u8> + 'static;
|
|
||||||
|
|
||||||
/// Return a scalar from its fixed-length bytes representation, without
|
|
||||||
/// checking if the scalar is zero.
|
|
||||||
fn from_scalar_slice_unchecked(
|
|
||||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
|
||||||
) -> Result<Self::Scalar, InternalError>;
|
|
||||||
|
|
||||||
/// Return a scalar from its fixed-length bytes representation. If the scalar
|
|
||||||
/// is zero, then return an error.
|
|
||||||
fn from_scalar_slice(
|
|
||||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
|
||||||
) -> Result<Self::Scalar, InternalError> {
|
|
||||||
let scalar = Self::from_scalar_slice_unchecked(scalar_bits)?;
|
|
||||||
if Self::ct_equal_scalar(&scalar, &Self::scalar_zero()) {
|
|
||||||
return Err(InternalError::ZeroScalarError);
|
|
||||||
}
|
|
||||||
Ok(scalar)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// picks a scalar at random
|
|
||||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
|
||||||
/// Serializes a scalar to bytes
|
|
||||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
|
|
||||||
/// The multiplicative inverse of this scalar
|
|
||||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
|
|
||||||
|
|
||||||
/// The byte length necessary to represent group elements
|
/// The byte length necessary to represent group elements
|
||||||
type ElemLen: ArrayLength<u8> + 'static;
|
type ElemLen: ArrayLength<u8> + 'static;
|
||||||
|
|
||||||
/// Return an element from its fixed-length bytes representation. This is
|
/// The type of base field scalars
|
||||||
/// the unchecked version, which does not check for deserializing the identity
|
type Scalar: ConstantTimeEq
|
||||||
/// element
|
+ Copy
|
||||||
fn from_element_slice_unchecked(
|
+ Zeroize
|
||||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
|
||||||
) -> Result<Self, InternalError>;
|
+ 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
|
/// The byte length necessary to represent scalars
|
||||||
/// is the identity element, return an error.
|
type ScalarLen: ArrayLength<u8> + 'static;
|
||||||
fn from_element_slice(
|
|
||||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
|
||||||
) -> Result<Self, InternalError> {
|
|
||||||
let elem = Self::from_element_slice_unchecked(element_bits)?;
|
|
||||||
|
|
||||||
if Self::ct_equal(&elem, &<Self as Group>::identity()) {
|
/// Transforms a password and domain separation tag (DST) into a curve point
|
||||||
// found the identity element
|
///
|
||||||
return Err(InternalError::PointError);
|
/// # Errors
|
||||||
}
|
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
|
||||||
|
/// then [`u16::MAX`].
|
||||||
|
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
|
||||||
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
|
||||||
|
|
||||||
Ok(elem)
|
/// Hashes a slice of pseudo-random bytes to a scalar
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer
|
||||||
|
/// then [`u16::MAX`].
|
||||||
|
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError>
|
||||||
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
|
||||||
|
|
||||||
|
/// Get the base point for the group
|
||||||
|
fn base_elem() -> Self::Elem;
|
||||||
|
|
||||||
|
/// Returns the identity group element
|
||||||
|
fn identity_elem() -> Self::Elem;
|
||||||
|
|
||||||
|
/// Returns `true` if the element is equal to the identity element
|
||||||
|
fn is_identity_elem(elem: Self::Elem) -> Choice {
|
||||||
|
Self::identity_elem().ct_eq(&elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the `self` group element
|
/// Serializes the `self` group element
|
||||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
|
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen>;
|
||||||
|
|
||||||
/// Get the base point for the group
|
/// Return an element from its fixed-length bytes representation. If the
|
||||||
fn base_point() -> Self;
|
/// 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)
|
/// picks a scalar at random
|
||||||
fn is_identity(&self) -> bool {
|
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||||
self.ct_equal(&<Self as Group>::identity())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the identity group element
|
/// The multiplicative inverse of this scalar
|
||||||
fn identity() -> Self;
|
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
|
/// Returns the scalar representing zero
|
||||||
fn scalar_zero() -> Self::Scalar;
|
#[cfg(test)]
|
||||||
|
fn zero_scalar() -> Self::Scalar;
|
||||||
|
|
||||||
/// Compares in constant time if the group elements are equal
|
/// Serializes a scalar to bytes
|
||||||
fn ct_equal(&self, other: &Self) -> bool;
|
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
|
||||||
|
|
||||||
/// Compares in constant time if the scalars are equal
|
/// Return a scalar from its fixed-length bytes representation. If the
|
||||||
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool;
|
/// scalar is zero or invalid, then return an error.
|
||||||
|
///
|
||||||
/// Set the contents of self to the identity value
|
/// # Errors
|
||||||
fn zeroize(&mut self) {
|
/// [`Error::Deserialization`](crate::Error::Deserialization) if the scalar
|
||||||
*self = <Self as Group>::identity();
|
/// is not a valid point on the group or zero.
|
||||||
}
|
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,555 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
// Note: This group implementation of p256 is experimental for now,
|
|
||||||
// until hash-to-curve or crypto-bigint are fully supported.
|
|
||||||
|
|
||||||
#![allow(
|
|
||||||
clippy::borrow_interior_mutable_const,
|
|
||||||
clippy::declare_interior_mutable_const
|
|
||||||
)]
|
|
||||||
|
|
||||||
use super::Group;
|
|
||||||
use crate::errors::InternalError;
|
|
||||||
use crate::hash::Hash;
|
|
||||||
use core::ops::{Add, Div, Mul, Neg, Sub};
|
|
||||||
use core::str::FromStr;
|
|
||||||
use generic_array::typenum::{U32, U33};
|
|
||||||
use generic_array::{ArrayLength, GenericArray};
|
|
||||||
use num_bigint::{BigInt, Sign};
|
|
||||||
use num_integer::Integer;
|
|
||||||
use num_traits::{One, ToPrimitive, Zero};
|
|
||||||
use once_cell::unsync::Lazy;
|
|
||||||
use p256_::elliptic_curve::group::prime::PrimeCurveAffine;
|
|
||||||
use p256_::elliptic_curve::group::GroupEncoding;
|
|
||||||
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
|
||||||
use p256_::elliptic_curve::subtle::ConstantTimeEq;
|
|
||||||
use p256_::elliptic_curve::Field;
|
|
||||||
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
|
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
|
|
||||||
// `L: 48`
|
|
||||||
pub const L: usize = 48;
|
|
||||||
|
|
||||||
impl Group for ProjectivePoint {
|
|
||||||
const SUITE_ID: usize = 0x0003;
|
|
||||||
|
|
||||||
// Implements the `hash_to_curve()` function from
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
|
||||||
fn hash_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
|
|
||||||
// `p: 2^256 - 2^224 + 2^192 + 2^96 - 1`
|
|
||||||
const P: Lazy<BigInt> = Lazy::new(|| {
|
|
||||||
BigInt::from_str(
|
|
||||||
"115792089210356248762697446949407573530086143415290314195533631308867097853951",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
// `A: -3`
|
|
||||||
const A: Lazy<BigInt> = Lazy::new(|| BigInt::from(-3));
|
|
||||||
// `B: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`
|
|
||||||
const B: Lazy<BigInt> = Lazy::new(|| {
|
|
||||||
BigInt::parse_bytes(
|
|
||||||
b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
|
|
||||||
16,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
// `Z: -10`
|
|
||||||
const Z: Lazy<BigInt> = Lazy::new(|| BigInt::from(-10));
|
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
|
||||||
// `hash_to_curve` calls `hash_to_field` with a `count` of `2`
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
|
||||||
// `hash_to_field` calls `expand_message` with a `len_in_bytes` of `count * L`
|
|
||||||
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 2 * L)?;
|
|
||||||
|
|
||||||
// hash to curve
|
|
||||||
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L], &A, &B, &P, &Z);
|
|
||||||
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L..], &A, &B, &P, &Z);
|
|
||||||
|
|
||||||
// convert to `p256` types
|
|
||||||
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
|
||||||
&q0x, &q0y, false,
|
|
||||||
))
|
|
||||||
.ok_or(InternalError::PointError)?
|
|
||||||
.to_curve();
|
|
||||||
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
|
||||||
&q1x, &q1y, false,
|
|
||||||
))
|
|
||||||
.ok_or(InternalError::PointError)?;
|
|
||||||
|
|
||||||
Ok(p0 + p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements the `HashToScalar()` function from
|
|
||||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3
|
|
||||||
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
|
|
||||||
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#[{%22num%22:211,%22gen%22:0},{%22name%22:%22XYZ%22},70,700,0]
|
|
||||||
// P-256 `n` is defined as `115792089210356248762697446949407573529996955224135760342 422259061068512044369`
|
|
||||||
const N: once_cell::unsync::Lazy<BigInt> = once_cell::unsync::Lazy::new(|| {
|
|
||||||
BigInt::from_str(
|
|
||||||
"115792089210356248762697446949407573529996955224135760342422259061068512044369",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
|
||||||
// `HashToScalar` is `hash_to_field`
|
|
||||||
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, L)?;
|
|
||||||
let mut bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
|
|
||||||
.mod_floor(&N)
|
|
||||||
.to_bytes_be()
|
|
||||||
.1;
|
|
||||||
bytes.resize(32, 0);
|
|
||||||
|
|
||||||
Ok(p256_::Scalar::from_bytes_reduced(GenericArray::from_slice(
|
|
||||||
&bytes,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type ElemLen = U33;
|
|
||||||
type Scalar = p256_::Scalar;
|
|
||||||
type ScalarLen = U32;
|
|
||||||
|
|
||||||
fn from_scalar_slice_unchecked(
|
|
||||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
|
||||||
) -> Result<Self::Scalar, InternalError> {
|
|
||||||
Ok(Self::Scalar::from_bytes_reduced(scalar_bits))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
|
||||||
Self::Scalar::random(rng)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
|
||||||
scalar.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
|
||||||
scalar.invert().unwrap_or(Self::Scalar::zero())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_element_slice_unchecked(
|
|
||||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
|
||||||
) -> Result<Self, InternalError> {
|
|
||||||
Option::from(Self::from_bytes(element_bits)).ok_or(InternalError::PointError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
|
|
||||||
let mut bytes = self.to_affine().to_encoded_point(true).as_bytes().to_vec();
|
|
||||||
bytes.resize(33, 0);
|
|
||||||
*GenericArray::from_slice(&bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn base_point() -> Self {
|
|
||||||
Self::generator()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn identity() -> Self {
|
|
||||||
Self::identity()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scalar_zero() -> Self::Scalar {
|
|
||||||
Self::Scalar::zero()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ct_equal(&self, other: &Self) -> bool {
|
|
||||||
self.ct_eq(other).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
|
|
||||||
s1.ct_eq(s2).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to the hash_to_curve_simple_swu() function defined in
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-F.2>
|
|
||||||
///
|
|
||||||
/// `cmov`, `mod_floor` and `modpow` needs to be made constant-time, which
|
|
||||||
/// will be supported after crypto-bigint is no longer experimental. See
|
|
||||||
/// https://github.com/novifinancial/voprf/issues/13 for more context.
|
|
||||||
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
|
|
||||||
u: &[u8],
|
|
||||||
a: &BigInt,
|
|
||||||
b: &BigInt,
|
|
||||||
p: &BigInt,
|
|
||||||
z: &BigInt,
|
|
||||||
) -> (GenericArray<u8, N>, GenericArray<u8, N>) {
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Field<'a>(&'a BigInt);
|
|
||||||
|
|
||||||
impl<'a> Field<'a> {
|
|
||||||
fn new(p: &'a BigInt) -> Self {
|
|
||||||
Self(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn element(&'a self, number: &BigInt) -> FieldElement<'a> {
|
|
||||||
FieldElement {
|
|
||||||
number: number.mod_floor(self.0),
|
|
||||||
f: self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn one(&'a self) -> FieldElement<'a> {
|
|
||||||
self.element(&BigInt::one())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
|
|
||||||
fn inv0(&'a self, number: &FieldElement<'a>) -> FieldElement<'a> {
|
|
||||||
number.pow_internal(&(self.0 - 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finite field arithmetic
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct FieldElement<'a> {
|
|
||||||
number: BigInt,
|
|
||||||
f: &'a Field<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Add for FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
&self + &rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Add for &FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
self.f.element(&(&self.number + &rhs.number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Sub for &FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn sub(self, rhs: Self) -> Self::Output {
|
|
||||||
self.f.element(&(&self.number - &rhs.number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Neg for FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
-&self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Neg for &FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
self.f.element(&-&self.number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Mul for FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn mul(self, rhs: Self) -> Self::Output {
|
|
||||||
&self * &rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Mul<&Self> for FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn mul(self, rhs: &Self) -> Self::Output {
|
|
||||||
&self * rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Mul<FieldElement<'a>> for &FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn mul(self, rhs: FieldElement<'a>) -> Self::Output {
|
|
||||||
self * &rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Mul for &FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
fn mul(self, rhs: Self) -> Self::Output {
|
|
||||||
self.f.element(&(&self.number * &rhs.number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Div<&Self> for FieldElement<'a> {
|
|
||||||
type Output = FieldElement<'a>;
|
|
||||||
|
|
||||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
|
||||||
fn div(self, rhs: &Self) -> Self::Output {
|
|
||||||
self * rhs.f.inv0(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FieldElement<'a> {
|
|
||||||
fn square(&self) -> Self {
|
|
||||||
self * self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pow_internal(&self, exponent: &BigInt) -> Self {
|
|
||||||
let exponent = exponent.mod_floor(&(self.f.0 - 1));
|
|
||||||
self.f.element(&self.number.modpow(&exponent, self.f.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to the sqrt_3mod4() function defined in
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-I.1>
|
|
||||||
fn sqrt(&self) -> Self {
|
|
||||||
// constant
|
|
||||||
let c1 = (self.f.0 + 1) >> 2;
|
|
||||||
|
|
||||||
self.pow_internal(&c1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to the sgn0_m_eq_1() function defined in
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4.1>
|
|
||||||
fn sgn0(&self) -> i32 {
|
|
||||||
(&self.number % 2_usize).to_i32().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_zero(&self) -> bool {
|
|
||||||
self.number.is_zero()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to the is_square() function defined in
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
|
|
||||||
fn is_square(&self) -> bool {
|
|
||||||
// constant
|
|
||||||
let exponent = (self.f.0 - 1) >> 1;
|
|
||||||
|
|
||||||
let result = self.pow_internal(&exponent);
|
|
||||||
result.is_zero() || result.number.is_one()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_bytes<N: ArrayLength<u8>>(&self) -> GenericArray<u8, N> {
|
|
||||||
let val = self.number.mod_floor(self.f.0).to_bytes_be().1;
|
|
||||||
let mut bytes = alloc::vec![0u8; 32 - val.len()];
|
|
||||||
bytes.extend_from_slice(&val);
|
|
||||||
GenericArray::clone_from_slice(&bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cmov<'a>(x: &FieldElement<'a>, y: &FieldElement<'a>, b: bool) -> FieldElement<'a> {
|
|
||||||
if b {
|
|
||||||
y.clone()
|
|
||||||
} else {
|
|
||||||
x.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let f = Field::new(p);
|
|
||||||
let a = f.element(a);
|
|
||||||
let b = f.element(b);
|
|
||||||
let z = f.element(z);
|
|
||||||
let u = f.element(&BigInt::from_bytes_be(Sign::Plus, u));
|
|
||||||
|
|
||||||
// Constants:
|
|
||||||
// 1. c1 = -B / A
|
|
||||||
let c1 = -&b / &a;
|
|
||||||
// 2. c2 = -1 / Z
|
|
||||||
let c2 = -f.one() / &z;
|
|
||||||
|
|
||||||
// Steps:
|
|
||||||
// 1. tv1 = Z * u^2
|
|
||||||
let tv1 = z * u.square();
|
|
||||||
// 2. tv2 = tv1^2
|
|
||||||
let mut tv2 = tv1.square();
|
|
||||||
// 3. x1 = tv1 + tv2
|
|
||||||
let mut x1 = &tv1 + &tv2;
|
|
||||||
// 4. x1 = inv0(x1)
|
|
||||||
x1 = f.inv0(&x1);
|
|
||||||
// 5. e1 = x1 == 0
|
|
||||||
let e1 = x1.is_zero();
|
|
||||||
// 6. x1 = x1 + 1
|
|
||||||
x1 = x1 + f.one();
|
|
||||||
// 7. x1 = CMOV(x1, c2, e1) # If (tv1 + tv2) == 0, set x1 = -1 / Z
|
|
||||||
x1 = cmov(&x1, &c2, e1);
|
|
||||||
// 8. x1 = x1 * c1 # x1 = (-B / A) * (1 + (1 / (Z^2 * u^4 + Z * u^2)))
|
|
||||||
x1 = x1 * c1;
|
|
||||||
// 9. gx1 = x1^2
|
|
||||||
let mut gx1 = x1.square();
|
|
||||||
// 10. gx1 = gx1 + A
|
|
||||||
gx1 = gx1 + a;
|
|
||||||
// 11. gx1 = gx1 * x1
|
|
||||||
gx1 = gx1 * &x1;
|
|
||||||
// 12. gx1 = gx1 + B # gx1 = g(x1) = x1^3 + A * x1 + B
|
|
||||||
gx1 = gx1 + b;
|
|
||||||
// 13. x2 = tv1 * x1 # x2 = Z * u^2 * x1
|
|
||||||
let x2 = &tv1 * &x1;
|
|
||||||
// 14. tv2 = tv1 * tv2
|
|
||||||
tv2 = tv1 * tv2;
|
|
||||||
// 15. gx2 = gx1 * tv2 # gx2 = (Z * u^2)^3 * gx1
|
|
||||||
let gx2 = &gx1 * tv2;
|
|
||||||
// 16. e2 = is_square(gx1)
|
|
||||||
let e2 = gx1.is_square();
|
|
||||||
// 17. x = CMOV(x2, x1, e2) # If is_square(gx1), x = x1, else x = x2
|
|
||||||
let x = cmov(&x2, &x1, e2);
|
|
||||||
// 18. y2 = CMOV(gx2, gx1, e2) # If is_square(gx1), y2 = gx1, else y2 = gx2
|
|
||||||
let y2 = cmov(&gx2, &gx1, e2);
|
|
||||||
// 19. y = sqrt(y2)
|
|
||||||
let mut y = y2.sqrt();
|
|
||||||
// 20. e3 = sgn0(u) == sgn0(y) # Fix sign of y
|
|
||||||
let e3 = u.sgn0() == y.sgn0();
|
|
||||||
// 21. y = CMOV(-y, y, e3)
|
|
||||||
y = cmov(&-&y, &y, e3);
|
|
||||||
// 22. return (x, y)
|
|
||||||
(x.to_bytes(), y.to_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
struct Params {
|
|
||||||
msg: &'static str,
|
|
||||||
px: &'static str,
|
|
||||||
py: &'static str,
|
|
||||||
u0: &'static str,
|
|
||||||
u1: &'static str,
|
|
||||||
q0x: &'static str,
|
|
||||||
q0y: &'static str,
|
|
||||||
q1x: &'static str,
|
|
||||||
q1y: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_to_curve_simple_swu() {
|
|
||||||
const P: Lazy<BigInt> = Lazy::new(|| {
|
|
||||||
BigInt::from_str(
|
|
||||||
"115792089210356248762697446949407573530086143415290314195533631308867097853951",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
const A: Lazy<BigInt> = Lazy::new(|| BigInt::from(-3));
|
|
||||||
const B: Lazy<BigInt> = Lazy::new(|| {
|
|
||||||
BigInt::parse_bytes(
|
|
||||||
b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
|
|
||||||
16,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
const Z: Lazy<BigInt> = Lazy::new(|| BigInt::from(-10));
|
|
||||||
|
|
||||||
// Test vectors taken from https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.1.1
|
|
||||||
let test_vectors = alloc::vec![
|
|
||||||
Params {
|
|
||||||
msg: "",
|
|
||||||
px: "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4",
|
|
||||||
py: "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415",
|
|
||||||
u0: "ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009",
|
|
||||||
u1: "8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a",
|
|
||||||
q0x: "ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5",
|
|
||||||
q0y: "dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1",
|
|
||||||
q1x: "51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5",
|
|
||||||
q1y: "b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abc",
|
|
||||||
px: "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f",
|
|
||||||
py: "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e",
|
|
||||||
u0: "afe47f2ea2b10465cc26ac403194dfb68b7f5ee865cda61e9f3e07a537220af1",
|
|
||||||
u1: "379a27833b0bfe6f7bdca08e1e83c760bf9a338ab335542704edcd69ce9e46e0",
|
|
||||||
q0x: "5219ad0ddef3cc49b714145e91b2f7de6ce0a7a7dc7406c7726c7e373c58cb48",
|
|
||||||
q0y: "7950144e52d30acbec7b624c203b1996c99617d0b61c2442354301b191d93ecf",
|
|
||||||
q1x: "019b7cb4efcfeaf39f738fe638e31d375ad6837f58a852d032ff60c69ee3875f",
|
|
||||||
q1y: "589a62d2b22357fed5449bc38065b760095ebe6aeac84b01156ee4252715446e",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "abcdef0123456789",
|
|
||||||
px: "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80",
|
|
||||||
py: "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3",
|
|
||||||
u0: "0fad9d125a9477d55cf9357105b0eb3a5c4259809bf87180aa01d651f53d312c",
|
|
||||||
u1: "b68597377392cd3419d8fcc7d7660948c8403b19ea78bbca4b133c9d2196c0fb",
|
|
||||||
q0x: "a17bdf2965eb88074bc01157e644ed409dac97cfcf0c61c998ed0fa45e79e4a2",
|
|
||||||
q0y: "4f1bc80c70d411a3cc1d67aeae6e726f0f311639fee560c7f5a664554e3c9c2e",
|
|
||||||
q1x: "7da48bb67225c1a17d452c983798113f47e438e4202219dd0715f8419b274d66",
|
|
||||||
q1y: "b765696b2913e36db3016c47edb99e24b1da30e761a8a3215dc0ec4d8f96e6f9",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
|
||||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
||||||
px: "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d",
|
|
||||||
py: "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e",
|
|
||||||
u0: "3bbc30446f39a7befad080f4d5f32ed116b9534626993d2cc5033f6f8d805919",
|
|
||||||
u1: "76bb02db019ca9d3c1e02f0c17f8baf617bbdae5c393a81d9ce11e3be1bf1d33",
|
|
||||||
q0x: "c76aaa823aeadeb3f356909cb08f97eee46ecb157c1f56699b5efebddf0e6398",
|
|
||||||
q0y: "776a6f45f528a0e8d289a4be12c4fab80762386ec644abf2bffb9b627e4352b1",
|
|
||||||
q1x: "418ac3d85a5ccc4ea8dec14f750a3a9ec8b85176c95a7022f391826794eb5a75",
|
|
||||||
q1y: "fd6604f69e9d9d2b74b072d14ea13050db72c932815523305cb9e807cc900aff",
|
|
||||||
},
|
|
||||||
Params {
|
|
||||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
px: "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5",
|
|
||||||
py: "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc",
|
|
||||||
u0: "4ebc95a6e839b1ae3c63b847798e85cb3c12d3817ec6ebc10af6ee51adb29fec",
|
|
||||||
u1: "4e21af88e22ea80156aff790750121035b3eefaa96b425a8716e0d20b4e269ee",
|
|
||||||
q0x: "d88b989ee9d1295df413d4456c5c850b8b2fb0f5402cc5c4c7e815412e926db8",
|
|
||||||
q0y: "bb4a1edeff506cf16def96afff41b16fc74f6dbd55c2210e5b8f011ba32f4f40",
|
|
||||||
q1x: "a281e34e628f3a4d2a53fa87ff973537d68ad4fbc28d3be5e8d9f6a2571c5a4b",
|
|
||||||
q1y: "f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let dst = "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
|
|
||||||
|
|
||||||
for tv in test_vectors {
|
|
||||||
let uniform_bytes = super::super::expand::expand_message_xmd::<sha2::Sha256>(
|
|
||||||
tv.msg.as_bytes(),
|
|
||||||
dst.as_bytes(),
|
|
||||||
96,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[..48]).mod_floor(&P);
|
|
||||||
let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[48..]).mod_floor(&P);
|
|
||||||
|
|
||||||
assert_eq!(BigInt::parse_bytes(tv.u0.as_bytes(), 16).unwrap(), u0);
|
|
||||||
assert_eq!(BigInt::parse_bytes(tv.u1.as_bytes(), 16).unwrap(), u1);
|
|
||||||
|
|
||||||
let (q0x, q0y) = super::hash_to_curve_simple_swu(&u0.to_bytes_be().1, &A, &B, &P, &Z);
|
|
||||||
let (q1x, q1y) = super::hash_to_curve_simple_swu(&u1.to_bytes_be().1, &A, &B, &P, &Z);
|
|
||||||
|
|
||||||
assert_eq!(tv.q0x, hex::encode(q0x));
|
|
||||||
assert_eq!(tv.q0y, hex::encode(q0y));
|
|
||||||
assert_eq!(tv.q1x, hex::encode(q1x));
|
|
||||||
assert_eq!(tv.q1y, hex::encode(q1y));
|
|
||||||
|
|
||||||
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
|
||||||
&q0x, &q0y, false,
|
|
||||||
))
|
|
||||||
.unwrap()
|
|
||||||
.to_curve();
|
|
||||||
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
|
||||||
&q1x, &q1y, false,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let p = (p0 + p1).to_encoded_point(false);
|
|
||||||
|
|
||||||
assert_eq!(tv.px, hex::encode(p.x().unwrap()));
|
|
||||||
assert_eq!(tv.py, hex::encode(p.y().unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+97
-85
@@ -5,76 +5,104 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
use super::Group;
|
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||||
use crate::errors::InternalError;
|
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
|
||||||
use crate::hash::Hash;
|
use curve25519_dalek::scalar::Scalar;
|
||||||
use core::convert::TryInto;
|
use curve25519_dalek::traits::Identity;
|
||||||
use curve25519_dalek::{
|
use digest::core_api::BlockSizeUser;
|
||||||
constants::RISTRETTO_BASEPOINT_POINT,
|
use digest::Digest;
|
||||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
|
||||||
scalar::Scalar,
|
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
|
||||||
traits::Identity,
|
use generic_array::GenericArray;
|
||||||
};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
use generic_array::{typenum::U32, GenericArray};
|
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
/// The implementation of such a subgroup for Ristretto
|
use super::Group;
|
||||||
impl Group for RistrettoPoint {
|
use crate::{Error, InternalError, Result};
|
||||||
const SUITE_ID: usize = 0x0001;
|
|
||||||
|
/// [`Group`] implementation for Ristretto255.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
pub struct Ristretto255;
|
||||||
|
|
||||||
|
#[cfg(feature = "ristretto255-ciphersuite")]
|
||||||
|
impl crate::CipherSuite for Ristretto255 {
|
||||||
|
const ID: u16 = 0x0001;
|
||||||
|
|
||||||
|
type Group = Ristretto255;
|
||||||
|
|
||||||
|
type Hash = sha2::Sha512;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
impl Group for Ristretto255 {
|
||||||
|
type Elem = RistrettoPoint;
|
||||||
|
|
||||||
|
type ElemLen = U32;
|
||||||
|
|
||||||
|
type Scalar = Scalar;
|
||||||
|
|
||||||
|
type ScalarLen = U32;
|
||||||
|
|
||||||
// Implements the `hash_to_ristretto255()` function from
|
// Implements the `hash_to_ristretto255()` function from
|
||||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
||||||
fn hash_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
|
fn hash_to_curve<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Elem, InternalError>
|
||||||
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 64)?;
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||||
|
{
|
||||||
|
let mut uniform_bytes = GenericArray::<_, U64>::default();
|
||||||
|
ExpandMsgXmd::<H>::expand_message(input, dst, 64)
|
||||||
|
.map_err(|_| InternalError::Input)?
|
||||||
|
.fill_bytes(&mut uniform_bytes);
|
||||||
|
|
||||||
Ok(RistrettoPoint::from_uniform_bytes(
|
Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes.into()))
|
||||||
uniform_bytes
|
|
||||||
.as_slice()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| InternalError::HashToCurveError)?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the `HashToScalar()` function from
|
// Implements the `HashToScalar()` function from
|
||||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1
|
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1
|
||||||
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
|
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Scalar, InternalError>
|
||||||
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, 64)?;
|
where
|
||||||
|
H: Digest + BlockSizeUser,
|
||||||
|
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||||
|
{
|
||||||
|
let mut uniform_bytes = GenericArray::<_, U64>::default();
|
||||||
|
ExpandMsgXmd::<H>::expand_message(input, dst, 64)
|
||||||
|
.map_err(|_| InternalError::Input)?
|
||||||
|
.fill_bytes(&mut uniform_bytes);
|
||||||
|
|
||||||
Ok(Scalar::from_bytes_mod_order_wide(
|
Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes.into()))
|
||||||
uniform_bytes
|
|
||||||
.as_slice()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| InternalError::HashToCurveError)?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scalar = Scalar;
|
fn base_elem() -> Self::Elem {
|
||||||
type ScalarLen = U32;
|
RISTRETTO_BASEPOINT_POINT
|
||||||
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 {
|
fn identity_elem() -> Self::Elem {
|
||||||
|
RistrettoPoint::identity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialization of a group element
|
||||||
|
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
|
||||||
|
elem.compress().to_bytes().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_elem(element_bits: &[u8]) -> Result<Self::Elem> {
|
||||||
|
if element_bits.len() != 32 {
|
||||||
|
return Err(Error::Deserialization);
|
||||||
|
}
|
||||||
|
|
||||||
|
CompressedRistretto::from_slice(element_bits)
|
||||||
|
.decompress()
|
||||||
|
.filter(|point| point != &RistrettoPoint::identity())
|
||||||
|
.ok_or(Error::Deserialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||||
loop {
|
loop {
|
||||||
let scalar = {
|
let scalar = Scalar::random(rng);
|
||||||
#[cfg(not(test))]
|
|
||||||
{
|
|
||||||
let mut scalar_bytes = [0u8; 64];
|
|
||||||
rng.fill_bytes(&mut scalar_bytes);
|
|
||||||
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
|
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
let mut scalar_bytes = [0u8; 32];
|
|
||||||
rng.fill_bytes(&mut scalar_bytes);
|
|
||||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if scalar != Scalar::zero() {
|
if scalar != Scalar::zero() {
|
||||||
break scalar;
|
break scalar;
|
||||||
@@ -82,45 +110,29 @@ impl Group for RistrettoPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
|
||||||
scalar.to_bytes().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
|
||||||
scalar.invert()
|
scalar.invert()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The byte length necessary to represent group elements
|
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
|
||||||
type ElemLen = U32;
|
scalar.ct_eq(&Scalar::zero())
|
||||||
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 {
|
#[cfg(test)]
|
||||||
RISTRETTO_BASEPOINT_POINT
|
fn zero_scalar() -> Self::Scalar {
|
||||||
|
Scalar::zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identity() -> Self {
|
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
||||||
<Self as Identity>::identity()
|
scalar.to_bytes().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scalar_zero() -> Self::Scalar {
|
fn deserialize_scalar(scalar_bits: &[u8]) -> Result<Self::Scalar> {
|
||||||
Self::Scalar::zero()
|
scalar_bits
|
||||||
}
|
.try_into()
|
||||||
|
.ok()
|
||||||
fn ct_equal(&self, other: &Self) -> bool {
|
.and_then(Scalar::from_canonical_bytes)
|
||||||
ConstantTimeEq::ct_eq(self, other).into()
|
.filter(|scalar| scalar != &Scalar::zero())
|
||||||
}
|
.ok_or(Error::Deserialization)
|
||||||
|
|
||||||
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
|
|
||||||
ConstantTimeEq::ct_eq(s1, s2).into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-28
@@ -7,51 +7,43 @@
|
|||||||
|
|
||||||
//! Includes a series of tests for the group implementations
|
//! Includes a series of tests for the group implementations
|
||||||
|
|
||||||
use crate::errors::InternalError;
|
use crate::{Error, Group, Result};
|
||||||
use crate::group::Group;
|
|
||||||
use crate::CipherSuite;
|
|
||||||
|
|
||||||
// Test that the deserialization of a group element should throw an error
|
// Test that the deserialization of a group element should throw an error if the
|
||||||
// if the identity element can be deserialized properly
|
// identity element can be deserialized properly
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_group_properties() -> Result<(), InternalError> {
|
fn test_group_properties() -> Result<()> {
|
||||||
use crate::tests::Ristretto255Sha512;
|
use p256::NistP256;
|
||||||
|
|
||||||
test_identity_element_error::<Ristretto255Sha512>()?;
|
#[cfg(feature = "ristretto255")]
|
||||||
test_zero_scalar_error::<Ristretto255Sha512>()?;
|
|
||||||
|
|
||||||
#[cfg(feature = "p256")]
|
|
||||||
{
|
{
|
||||||
use crate::tests::P256Sha256;
|
use crate::Ristretto255;
|
||||||
|
|
||||||
test_identity_element_error::<P256Sha256>()?;
|
test_identity_element_error::<Ristretto255>()?;
|
||||||
test_zero_scalar_error::<P256Sha256>()?;
|
test_zero_scalar_error::<Ristretto255>()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_identity_element_error::<NistP256>()?;
|
||||||
|
test_zero_scalar_error::<NistP256>()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that the identity element cannot be deserialized
|
// Checks that the identity element cannot be deserialized
|
||||||
fn test_identity_element_error<CS: CipherSuite>() -> Result<(), InternalError> {
|
fn test_identity_element_error<G: Group>() -> Result<()> {
|
||||||
let identity = CS::Group::identity();
|
let identity = G::identity_elem();
|
||||||
let result = CS::Group::from_element_slice(&identity.to_arr());
|
let result = G::deserialize_elem(&G::serialize_elem(identity));
|
||||||
assert!(match result {
|
assert!(matches!(result, Err(Error::Deserialization)));
|
||||||
Err(InternalError::PointError) => true,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that the zero scalar cannot be deserialized
|
// Checks that the zero scalar cannot be deserialized
|
||||||
fn test_zero_scalar_error<CS: CipherSuite>() -> Result<(), InternalError> {
|
fn test_zero_scalar_error<G: Group>() -> Result<()> {
|
||||||
let zero_scalar = CS::Group::scalar_zero();
|
let zero_scalar = G::zero_scalar();
|
||||||
let result = CS::Group::from_scalar_slice(&CS::Group::scalar_as_bytes(zero_scalar));
|
let result = G::deserialize_scalar(&G::serialize_scalar(zero_scalar));
|
||||||
assert!(match result {
|
assert!(matches!(result, Err(Error::Deserialization)));
|
||||||
Err(InternalError::ZeroScalarError) => true,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
//! A convenience trait for digest bounds used throughout the library
|
|
||||||
|
|
||||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
|
||||||
|
|
||||||
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
|
|
||||||
// Associated types could be simplified when they are made as defaults:
|
|
||||||
// https://github.com/rust-lang/rust/issues/29661
|
|
||||||
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
|
|
||||||
|
|
||||||
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
|
|
||||||
-203
@@ -1,203 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
macro_rules! impl_debug_eq_hash_for {
|
|
||||||
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: core::fmt::Debug,)+)?
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_struct("$name")
|
|
||||||
.field("$field1", &self.$field1)
|
|
||||||
$(.field("$field2", &self.$field2))*
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: Eq,)+)?
|
|
||||||
{}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: PartialEq,)+)?
|
|
||||||
{
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
PartialEq::eq(&self.$field1, &other.$field1)
|
|
||||||
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: core::hash::Hash,)+)?
|
|
||||||
{
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
core::hash::Hash::hash(&self.$field1, state);
|
|
||||||
$(core::hash::Hash::hash(&self.$field2, state);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: core::fmt::Debug,)+)?
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_tuple("$name")
|
|
||||||
.field(&self.$field1)
|
|
||||||
$(.field(&self.$field2))*
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: Eq,)+)?
|
|
||||||
{}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: PartialEq,)+)?
|
|
||||||
{
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
PartialEq::eq(&self.$field1, &other.$field1)
|
|
||||||
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: core::hash::Hash,)+)?
|
|
||||||
{
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
core::hash::Hash::hash(&self.$field1, state);
|
|
||||||
$(core::hash::Hash::hash(&self.$field2, state);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_clone_for {
|
|
||||||
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: Clone,)+)?
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
$field1: self.$field1.clone(),
|
|
||||||
$($field2: self.$field2.clone(),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
|
|
||||||
$(where $($type: Clone,)+)?
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(
|
|
||||||
self.$field1.clone(),
|
|
||||||
$(self.$field2.clone(),)*
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_zeroize_on_drop_for {
|
|
||||||
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)?
|
|
||||||
{
|
|
||||||
fn zeroize(&mut self) {
|
|
||||||
self.$field1.zeroize();
|
|
||||||
$(self.$field2.zeroize();)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl$(<$($gen$(: $bound)?),+>)? Drop for $name$(<$($gen),+>)?
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
self.$field1.zeroize();
|
|
||||||
$(self.$field2.zeroize();)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
|
|
||||||
macro_rules! impl_serialize_and_deserialize_for {
|
|
||||||
($t:ident) => {
|
|
||||||
#[cfg(feature = "serialize")]
|
|
||||||
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
if serializer.is_human_readable() {
|
|
||||||
serializer.serialize_str(&base64::encode(&self.serialize()))
|
|
||||||
} else {
|
|
||||||
serializer.serialize_bytes(&self.serialize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
|
||||||
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
if deserializer.is_human_readable() {
|
|
||||||
let s = <&str>::deserialize(deserializer)?;
|
|
||||||
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
|
|
||||||
.map_err(serde::de::Error::custom)
|
|
||||||
} else {
|
|
||||||
struct ByteVisitor<CS: CipherSuite> {
|
|
||||||
marker: core::marker::PhantomData<CS>,
|
|
||||||
}
|
|
||||||
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
|
|
||||||
type Value = $t<CS>;
|
|
||||||
fn expecting(
|
|
||||||
&self,
|
|
||||||
formatter: &mut core::fmt::Formatter,
|
|
||||||
) -> core::fmt::Result {
|
|
||||||
formatter.write_str(core::concat!(
|
|
||||||
"the byte representation of a ",
|
|
||||||
core::stringify!($t)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
$t::<CS>::deserialize(value).map_err(|_| {
|
|
||||||
serde::de::Error::invalid_value(
|
|
||||||
serde::de::Unexpected::Bytes(value),
|
|
||||||
&core::concat!(
|
|
||||||
"invalid byte sequence for ",
|
|
||||||
core::stringify!($t)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deserializer.deserialize_bytes(ByteVisitor::<CS> {
|
|
||||||
marker: core::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience macro for implementing all of the above traits
|
|
||||||
macro_rules! impl_traits_for {
|
|
||||||
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
|
|
||||||
impl_debug_eq_hash_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
|
|
||||||
impl_clone_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
|
|
||||||
impl_zeroize_on_drop_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
|
|
||||||
impl_serialize_and_deserialize_for!($name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+317
-288
@@ -8,310 +8,265 @@
|
|||||||
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
|
//! An implementation of a verifiable oblivious pseudorandom function (VOPRF)
|
||||||
//!
|
//!
|
||||||
//! Note: This implementation is in sync with
|
//! Note: This implementation is in sync with
|
||||||
//! [draft-irtf-cfrg-voprf-07](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html),
|
//! [draft-irtf-cfrg-voprf-09](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html),
|
||||||
//! but this specification is subject to change, until the final version
|
//! but this specification is subject to change, until the final version
|
||||||
//! published by the IETF.
|
//! published by the IETF.
|
||||||
//!
|
//!
|
||||||
//! # Overview
|
//! # Overview
|
||||||
//!
|
//!
|
||||||
//! A verifiable oblivious pseudorandom function is a protocol that is
|
//! A verifiable oblivious pseudorandom function is a protocol that is evaluated
|
||||||
//! evaluated between a client and a server. They must first agree on a
|
//! between a client and a server. They must first agree on a finite cyclic
|
||||||
//! collection of primitives to be kept consistent throughout protocol
|
//! group along with a point representation.
|
||||||
//! execution. These include:
|
|
||||||
//! - a finite cyclic group along with a point representation, and
|
|
||||||
//! - a hashing function.
|
|
||||||
//!
|
//!
|
||||||
//! We will use the following choices in this example:
|
//! We will use the following choice in this example:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```ignore
|
||||||
//! use voprf::CipherSuite;
|
//! type CipherSuite = voprf::Ristretto255;
|
||||||
//! struct Default;
|
|
||||||
//! impl CipherSuite for Default {
|
|
||||||
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
|
||||||
//! type Hash = sha2::Sha512;
|
|
||||||
//! }
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Modes of Operation
|
//! ## Modes of Operation
|
||||||
//!
|
//!
|
||||||
//! VOPRF can be used in two modes:
|
//! VOPRF can be used in three modes:
|
||||||
//! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation with no
|
//! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation
|
||||||
//! support for the verification of the OPRF outputs
|
//! with no support for the verification of the OPRF outputs
|
||||||
//! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF evaluation where
|
//! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF
|
||||||
//! the outputs can be verified against a server public key
|
//! 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
|
//! In all of these modes, the protocol begins with a client blinding, followed
|
||||||
//! a server evaluation, and finishes with a client finalization.
|
//! by a server evaluation, and finishes with a client finalization.
|
||||||
//!
|
//!
|
||||||
//! ## Base Mode
|
//! ## Base Mode
|
||||||
//!
|
//!
|
||||||
//! In base mode, a [NonVerifiableClient] interacts with a
|
//! In base mode, an [OprfClient] interacts with an [OprfServer]
|
||||||
//! [NonVerifiableServer] to compute the output of the VOPRF.
|
//! to compute the output of the OPRF.
|
||||||
//!
|
//!
|
||||||
//! ### Server Setup
|
//! ### Server Setup
|
||||||
//!
|
//!
|
||||||
//! The protocol begins with a setup phase, in which the server must run
|
//! The protocol begins with a setup phase, in which the server must run
|
||||||
//! [NonVerifiableServer::new()] to produce an instance of itself. This
|
//! [OprfServer::new()] to produce an instance of itself. This instance
|
||||||
//! instance must be persisted on the server and used for online
|
//! must be persisted on the server and used for online client evaluations.
|
||||||
//! client evaluations.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! use rand::rngs::OsRng;
|
||||||
//! # }
|
//! use rand::RngCore;
|
||||||
//! use voprf::NonVerifiableServer;
|
//! use voprf::OprfServer;
|
||||||
//! use rand::{rngs::OsRng, RngCore};
|
|
||||||
//!
|
//!
|
||||||
//! let mut server_rng = OsRng;
|
//! let mut server_rng = OsRng;
|
||||||
//! let server = NonVerifiableServer::<Default>::new(&mut server_rng)
|
//! let server = OprfServer::<CipherSuite>::new(&mut server_rng);
|
||||||
//! .expect("Unable to construct server");
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Client Blinding
|
//! ### Client Blinding
|
||||||
//!
|
//!
|
||||||
//! In the first step, the client chooses an input, and runs
|
//! In the first step, the client chooses an input, and runs
|
||||||
//! [NonVerifiableClient::blind] to produce a [NonVerifiableClientBlindResult],
|
//! [OprfClient::blind] to produce an [OprfClientBlindResult],
|
||||||
//! which consists of a [BlindedElement] to be sent to the server and a
|
//! which consists of a [BlindedElement] to be sent to the server and an
|
||||||
//! [NonVerifiableClient] which must be persisted on the client for the final
|
//! [OprfClient] which must be persisted on the client for the final
|
||||||
//! step of the VOPRF protocol.
|
//! step of the VOPRF protocol.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! use rand::rngs::OsRng;
|
||||||
//! # }
|
//! use rand::RngCore;
|
||||||
//! use voprf::NonVerifiableClient;
|
//! use voprf::OprfClient;
|
||||||
//! use rand::{rngs::OsRng, RngCore};
|
|
||||||
//!
|
//!
|
||||||
//! let mut client_rng = OsRng;
|
//! let mut client_rng = OsRng;
|
||||||
//! let client_blind_result = NonVerifiableClient::<Default>::blind(
|
//! let client_blind_result = OprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
|
||||||
//! b"input",
|
//! .expect("Unable to construct client");
|
||||||
//! &mut client_rng,
|
|
||||||
//! ).expect("Unable to construct client");
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Server Evaluation
|
//! ### Server Evaluation
|
||||||
//!
|
//!
|
||||||
//! In the second step, the server takes as input the message from
|
//! In the second step, the server takes as input the message from
|
||||||
//! [NonVerifiableClient::blind] (a [BlindedElement]), and runs
|
//! [OprfClient::blind] (a [BlindedElement]), and runs
|
||||||
//! [NonVerifiableServer::evaluate] to produce a
|
//! [OprfServer::evaluate] to produce [EvaluationElement] to be sent to
|
||||||
//! [NonVerifiableServerEvaluateResult], which consists of an
|
//! the client.
|
||||||
//! [EvaluationElement] to be sent to the client.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::OprfClient;
|
||||||
//! # }
|
|
||||||
//! # use voprf::NonVerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
|
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # use voprf::NonVerifiableServer;
|
//! # use voprf::OprfServer;
|
||||||
//! # let mut server_rng = OsRng;
|
//! # let mut server_rng = OsRng;
|
||||||
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
//! let server_evaluate_result = server.evaluate(&client_blind_result.message);
|
||||||
//! use voprf::Metadata;
|
|
||||||
//! let server_evaluate_result = server.evaluate(
|
|
||||||
//! client_blind_result.message,
|
|
||||||
//! &Metadata::none(),
|
|
||||||
//! ).expect("Unable to perform server evaluate");
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Client Finalization
|
//! ### Client Finalization
|
||||||
//!
|
//!
|
||||||
//! In the final step, the client takes as input the message from
|
//! In the final step, the client takes as input the message from
|
||||||
//! [NonVerifiableServer::evaluate] (an [EvaluationElement]), and runs
|
//! [OprfServer::evaluate] (an [EvaluationElement]), and runs
|
||||||
//! [NonVerifiableClient::finalize] to produce a
|
//! [OprfClient::finalize] to produce an output for the protocol.
|
||||||
//! [NonVerifiableClientFinalizeResult], which consists of an
|
|
||||||
//! output for the protocol.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::OprfClient;
|
||||||
//! # }
|
|
||||||
//! # use voprf::NonVerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
|
//! # let client_blind_result = OprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # use voprf::NonVerifiableServer;
|
//! # use voprf::OprfServer;
|
||||||
//! # let mut server_rng = OsRng;
|
//! # let mut server_rng = OsRng;
|
||||||
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = OprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
//! # let message = server.evaluate(&client_blind_result.message);
|
||||||
//! # let server_evaluate_result = server.evaluate(
|
//! let client_finalize_result = client_blind_result
|
||||||
//! # client_blind_result.message,
|
//! .state
|
||||||
//! # &Metadata::none(),
|
//! .finalize(b"input", &message)
|
||||||
//! # ).expect("Unable to perform server evaluate");
|
//! .expect("Unable to perform client finalization");
|
||||||
//! use voprf::Metadata;
|
|
||||||
//! let client_finalize_result = client_blind_result.state.finalize(
|
|
||||||
//! server_evaluate_result.message,
|
|
||||||
//! &Metadata::none(),
|
|
||||||
//! ).expect("Unable to perform client finalization");
|
|
||||||
//!
|
//!
|
||||||
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
|
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Verifiable Mode
|
//! ## Verifiable Mode
|
||||||
//!
|
//!
|
||||||
//! In verifiable mode, a [VerifiableClient] interacts with a
|
//! In verifiable mode, a [VoprfClient] interacts with a [VoprfServer]
|
||||||
//! [VerifiableServer] to compute the output of the VOPRF. In order to
|
//! to compute the output of the VOPRF. In order to verify the server's
|
||||||
//! verify the server's computation, the client checks a server-generated
|
//! computation, the client checks a server-generated proof against the server's
|
||||||
//! proof against the server's public key. If the proof fails to verify,
|
//! public key. If the proof fails to verify, then the client does not receive
|
||||||
//! then the client does not receive an output.
|
//! an output.
|
||||||
//!
|
//!
|
||||||
//! In batch mode, a single proof can be used for multiple VOPRF evaluations.
|
//! In batch mode, a single proof can be used for multiple VOPRF evaluations.
|
||||||
//! See [the batching section](#batching)
|
//! See [the batching section](#batching) for more details on how to perform
|
||||||
//! for more details on how to perform batch evaluations.
|
//! batch evaluations.
|
||||||
//!
|
//!
|
||||||
//! ### Server Setup
|
//! ### Server Setup
|
||||||
//!
|
//!
|
||||||
//! The protocol begins with a setup phase, in which the server must run
|
//! The protocol begins with a setup phase, in which the server must run
|
||||||
//! [VerifiableServer::new()] to produce an instance of itself. This
|
//! [VoprfServer::new()] to produce an instance of itself. This instance
|
||||||
//! instance must be persisted on the server and used for online
|
//! must be persisted on the server and used for online client evaluations.
|
||||||
//! client evaluations.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! use rand::rngs::OsRng;
|
||||||
//! # }
|
//! use rand::RngCore;
|
||||||
//! use voprf::VerifiableServer;
|
//! use voprf::VoprfServer;
|
||||||
//! use rand::{rngs::OsRng, RngCore};
|
|
||||||
//!
|
//!
|
||||||
//! let mut server_rng = OsRng;
|
//! let mut server_rng = OsRng;
|
||||||
//! let server = VerifiableServer::<Default>::new(&mut server_rng)
|
//! let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! .expect("Unable to construct server");
|
|
||||||
//!
|
//!
|
||||||
//! // To be sent to the client
|
//! // To be sent to the client
|
||||||
//! println!("Server public key: {:?}", server.get_public_key());
|
//! println!("Server public key: {:?}", server.get_public_key());
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The public key should be sent to the client, since the client will
|
//! The public key should be sent to the client, since the client will need it
|
||||||
//! need it in the final step of the protocol in order to complete
|
//! in the final step of the protocol in order to complete the evaluation of the
|
||||||
//! the evaluation of the VOPRF.
|
//! VOPRF.
|
||||||
//!
|
//!
|
||||||
//! ### Client Blinding
|
//! ### Client Blinding
|
||||||
//!
|
//!
|
||||||
//! In the first step, the client chooses an input, and runs
|
//! In the first step, the client chooses an input, and runs
|
||||||
//! [VerifiableClient::blind] to produce a [VerifiableClientBlindResult],
|
//! [VoprfClient::blind] to produce a [VoprfClientBlindResult], which
|
||||||
//! which consists of a [BlindedElement] to be sent to the server and a
|
//! consists of a [BlindedElement] to be sent to the server and a
|
||||||
//! [VerifiableClient] which must be persisted on the client for the final
|
//! [VoprfClient] which must be persisted on the client for the final step
|
||||||
//! step of the VOPRF protocol.
|
//! of the VOPRF protocol.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! use rand::rngs::OsRng;
|
||||||
//! # }
|
//! use rand::RngCore;
|
||||||
//! use voprf::VerifiableClient;
|
//! use voprf::VoprfClient;
|
||||||
//! use rand::{rngs::OsRng, RngCore};
|
|
||||||
//!
|
//!
|
||||||
//! let mut client_rng = OsRng;
|
//! let mut client_rng = OsRng;
|
||||||
//! let client_blind_result = VerifiableClient::<Default>::blind(
|
//! let client_blind_result = VoprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
|
||||||
//! b"input",
|
//! .expect("Unable to construct client");
|
||||||
//! &mut client_rng,
|
|
||||||
//! ).expect("Unable to construct client");
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Server Evaluation
|
//! ### Server Evaluation
|
||||||
//!
|
//!
|
||||||
//! In the second step, the server takes as input the message from
|
//! In the second step, the server takes as input the message from
|
||||||
//! [VerifiableClient::blind] (a [BlindedElement]), and runs
|
//! [VoprfClient::blind] (a [BlindedElement]), and runs
|
||||||
//! [VerifiableServer::evaluate] to produce a
|
//! [VoprfServer::evaluate] to produce a [VoprfServerEvaluateResult],
|
||||||
//! [VerifiableServerEvaluateResult], which consists of an
|
//! which consists of an [EvaluationElement] to be sent to the client along with
|
||||||
//! [EvaluationElement] to be sent to the client along with a proof.
|
//! a proof.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::{VoprfServerEvaluateResult, VoprfClient};
|
||||||
//! # }
|
|
||||||
//! # use voprf::VerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let client_blind_result = VerifiableClient::<Default>::blind(
|
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # use voprf::VerifiableServer;
|
//! # use voprf::VoprfServer;
|
||||||
//! # let mut server_rng = OsRng;
|
//! # let mut server_rng = OsRng;
|
||||||
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
//! let VoprfServerEvaluateResult { message, proof } =
|
||||||
//! use voprf::Metadata;
|
//! server.evaluate(&mut server_rng, &client_blind_result.message);
|
||||||
//! let server_evaluate_result = server.evaluate(
|
|
||||||
//! &mut server_rng,
|
|
||||||
//! client_blind_result.message,
|
|
||||||
//! &Metadata::none(),
|
|
||||||
//! ).expect("Unable to perform server evaluate");
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Client Finalization
|
//! ### Client Finalization
|
||||||
//!
|
//!
|
||||||
//! In the final step, the client takes as input the message from
|
//! In the final step, the client takes as input the message from
|
||||||
//! [VerifiableServer::evaluate] (an [EvaluationElement]),
|
//! [VoprfServer::evaluate] (an [EvaluationElement]), the proof, and the
|
||||||
//! the proof, and the server's public key, and runs
|
//! server's public key, and runs [VoprfClient::finalize] to produce an
|
||||||
//! [VerifiableClient::finalize] to produce a
|
|
||||||
//! [VerifiableClientFinalizeResult], which consists of an
|
|
||||||
//! output for the protocol.
|
//! output for the protocol.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::VoprfClient;
|
||||||
//! # }
|
|
||||||
//! # use voprf::VerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let client_blind_result = VerifiableClient::<Default>::blind(
|
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # use voprf::VerifiableServer;
|
//! # use voprf::VoprfServer;
|
||||||
//! # let mut server_rng = OsRng;
|
//! # let mut server_rng = OsRng;
|
||||||
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
|
||||||
//! # let server_evaluate_result = server.evaluate(
|
//! # let server_evaluate_result = server.evaluate(
|
||||||
//! # &mut server_rng,
|
//! # &mut server_rng,
|
||||||
//! # client_blind_result.message,
|
//! # &client_blind_result.message,
|
||||||
//! # &Metadata::none(),
|
//! # );
|
||||||
//! # ).expect("Unable to perform server evaluate");
|
//! let client_finalize_result = client_blind_result
|
||||||
//! use voprf::Metadata;
|
//! .state
|
||||||
//! let client_finalize_result = client_blind_result.state.finalize(
|
//! .finalize(
|
||||||
//! server_evaluate_result.message,
|
//! b"input",
|
||||||
//! server_evaluate_result.proof,
|
//! &server_evaluate_result.message,
|
||||||
//! server.get_public_key(),
|
//! &server_evaluate_result.proof,
|
||||||
//! &Metadata::none(),
|
//! server.get_public_key(),
|
||||||
//! ).expect("Unable to perform client finalization");
|
//! )
|
||||||
|
//! .expect("Unable to perform client finalization");
|
||||||
//!
|
//!
|
||||||
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
|
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Advanced Usage
|
//! # Advanced Usage
|
||||||
@@ -322,167 +277,221 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Batching
|
//! ## Batching
|
||||||
//!
|
//!
|
||||||
//! It is sometimes desirable to generate only a single, constant-size
|
//! It is sometimes desirable to generate only a single, constant-size proof for
|
||||||
//! proof for an unbounded number of VOPRF evaluations (on arbitrary inputs).
|
//! an unbounded number of VOPRF evaluations (on arbitrary inputs).
|
||||||
//! [VerifiableClient] and [VerifiableServer] support a batch API for
|
//! [VoprfClient] and [VoprfServer] support a batch API for handling
|
||||||
//! handling this case. In the following example, we show how to use
|
//! this case. In the following example, we show how to use the batch API to
|
||||||
//! the batch API to produce a single proof for 10 parallel
|
//! produce a single proof for 10 parallel VOPRF evaluations.
|
||||||
//! VOPRF evaluations.
|
|
||||||
//!
|
//!
|
||||||
//! First, the client produces 10 blindings, storing their resulting
|
//! First, the client produces 10 blindings, storing their resulting states and
|
||||||
//! states and messages:
|
//! messages:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::VoprfClient;
|
||||||
//! # }
|
|
||||||
//! # use voprf::VerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! let mut client_rng = OsRng;
|
//! let mut client_rng = OsRng;
|
||||||
//! let mut client_states = vec![];
|
//! let mut client_states = vec![];
|
||||||
//! let mut client_messages = vec![];
|
//! let mut client_messages = vec![];
|
||||||
//! for _ in 0..10 {
|
//! for _ in 0..10 {
|
||||||
//! let client_blind_result = VerifiableClient::<Default>::blind(
|
//! let client_blind_result = VoprfClient::<CipherSuite>::blind(b"input", &mut client_rng)
|
||||||
//! b"input",
|
//! .expect("Unable to construct client");
|
||||||
//! &mut client_rng,
|
|
||||||
//! ).expect("Unable to construct client");
|
|
||||||
//! client_states.push(client_blind_result.state);
|
//! client_states.push(client_blind_result.state);
|
||||||
//! client_messages.push(client_blind_result.message);
|
//! client_messages.push(client_blind_result.message);
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Next, the server calls the [VerifiableServer::batch_evaluate]
|
//! Next, the server calls the [VoprfServer::batch_evaluate_prepare] and
|
||||||
//! function on a set of client messages, to produce a corresponding
|
//! [VoprfServer::batch_evaluate_finish] function on a set of client
|
||||||
//! set of messages to be returned to the client (returned in the same order),
|
//! messages, to produce a corresponding set of messages to be returned to the
|
||||||
//! along with a single proof:
|
//! client (returned in the same order), along with a single proof:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # struct Default;
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # impl CipherSuite for Default {
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # use voprf::{VoprfServerBatchEvaluateFinishResult, VoprfClient};
|
||||||
//! # }
|
|
||||||
//! # use voprf::VerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let mut client_states = vec![];
|
//! # let mut client_states = vec![];
|
||||||
//! # let mut client_messages = vec![];
|
//! # let mut client_messages = vec![];
|
||||||
//! # for _ in 0..10 {
|
//! # for _ in 0..10 {
|
||||||
//! # let client_blind_result = VerifiableClient::<Default>::blind(
|
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # client_states.push(client_blind_result.state);
|
//! # client_states.push(client_blind_result.state);
|
||||||
//! # client_messages.push(client_blind_result.message);
|
//! # client_messages.push(client_blind_result.message);
|
||||||
//! # }
|
//! # }
|
||||||
//! # use voprf::Metadata;
|
//! # use voprf::VoprfServer;
|
||||||
//! # use voprf::VerifiableServer;
|
|
||||||
//! let mut server_rng = OsRng;
|
//! let mut server_rng = OsRng;
|
||||||
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
//! let prepared_evaluation_elements = server.batch_evaluate_prepare(client_messages.iter());
|
||||||
//! let server_batch_evaluate_result = server.batch_evaluate(
|
//! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
|
||||||
//! &mut server_rng,
|
//! let VoprfServerBatchEvaluateFinishResult { messages, proof } = server
|
||||||
//! &client_messages,
|
//! .batch_evaluate_finish(&mut server_rng, client_messages.iter(), &prepared_elements)
|
||||||
//! &Metadata::none(),
|
//! .expect("Unable to perform server batch evaluate");
|
||||||
//! ).expect("Unable to perform server batch evaluate");
|
//! let messages: Vec<_> = messages.collect();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Then, the client calls [VerifiableClient::batch_finalize] on
|
//! If `alloc` is available, `VoprfServer::batch_evaluate` can be called
|
||||||
//! the client states saved from the first step, along with the messages
|
//! to avoid having to collect output manually:
|
||||||
//! returned by the server (constructing a [BatchFinalizeInput]), along with the
|
|
||||||
//! server's proof, in order to produce a vector of outputs if the proof
|
|
||||||
//! verifies correctly.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use voprf::CipherSuite;
|
//! # #[cfg(feature = "alloc")] {
|
||||||
//! # struct Default;
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
//! # impl CipherSuite for Default {
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
//! # type Hash = sha2::Sha512;
|
//! # type CipherSuite = p256::NistP256;
|
||||||
//! # }
|
//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient};
|
||||||
//! # use voprf::VerifiableClient;
|
|
||||||
//! # use rand::{rngs::OsRng, RngCore};
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
//! #
|
//! #
|
||||||
//! # let mut client_rng = OsRng;
|
//! # let mut client_rng = OsRng;
|
||||||
//! # let mut client_states = vec![];
|
//! # let mut client_states = vec![];
|
||||||
//! # let mut client_messages = vec![];
|
//! # let mut client_messages = vec![];
|
||||||
//! # for _ in 0..10 {
|
//! # for _ in 0..10 {
|
||||||
//! # let client_blind_result = VerifiableClient::<Default>::blind(
|
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
|
||||||
//! # b"input",
|
//! # b"input",
|
||||||
//! # &mut client_rng,
|
//! # &mut client_rng,
|
||||||
//! # ).expect("Unable to construct client");
|
//! # ).expect("Unable to construct client");
|
||||||
//! # client_states.push(client_blind_result.state);
|
//! # client_states.push(client_blind_result.state);
|
||||||
//! # client_messages.push(client_blind_result.message);
|
//! # client_messages.push(client_blind_result.message);
|
||||||
//! # }
|
//! # }
|
||||||
//! # use voprf::Metadata;
|
//! # use voprf::VoprfServer;
|
||||||
//! # use voprf::VerifiableServer;
|
|
||||||
//! use voprf::BatchFinalizeInput;
|
|
||||||
//! let mut server_rng = OsRng;
|
//! let mut server_rng = OsRng;
|
||||||
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
|
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
//! # .expect("Unable to construct server");
|
//! let VoprfServerBatchEvaluateResult { messages, proof } = server
|
||||||
//! # let server_batch_evaluate_result = server.batch_evaluate(
|
//! .batch_evaluate(&mut server_rng, &client_messages)
|
||||||
//! # &mut server_rng,
|
//! .expect("Unable to perform server batch evaluate");
|
||||||
//! # &client_messages,
|
//! # }
|
||||||
//! # &Metadata::none(),
|
//! ```
|
||||||
//! # ).expect("Unable to perform server batch evaluate");
|
//!
|
||||||
//! let batch_finalize_input = BatchFinalizeInput::new(
|
//! Then, the client calls [VoprfClient::batch_finalize] on the client
|
||||||
//! client_states,
|
//! states saved from the first step, along with the messages returned by the
|
||||||
//! server_batch_evaluate_result.messages,
|
//! server, along with the server's proof, in order to produce a vector of
|
||||||
//! );
|
//! outputs if the proof verifies correctly.
|
||||||
//! let client_batch_finalize_result = VerifiableClient::batch_finalize(
|
//!
|
||||||
//! batch_finalize_input,
|
//! ```
|
||||||
//! server_batch_evaluate_result.proof,
|
//! # #[cfg(feature = "alloc")] {
|
||||||
|
//! # #[cfg(feature = "ristretto255")]
|
||||||
|
//! # type CipherSuite = voprf::Ristretto255;
|
||||||
|
//! # #[cfg(not(feature = "ristretto255"))]
|
||||||
|
//! # type CipherSuite = p256::NistP256;
|
||||||
|
//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient};
|
||||||
|
//! # use rand::{rngs::OsRng, RngCore};
|
||||||
|
//! #
|
||||||
|
//! # let mut client_rng = OsRng;
|
||||||
|
//! # let mut client_states = vec![];
|
||||||
|
//! # let mut client_messages = vec![];
|
||||||
|
//! # for _ in 0..10 {
|
||||||
|
//! # let client_blind_result = VoprfClient::<CipherSuite>::blind(
|
||||||
|
//! # b"input",
|
||||||
|
//! # &mut client_rng,
|
||||||
|
//! # ).expect("Unable to construct client");
|
||||||
|
//! # client_states.push(client_blind_result.state);
|
||||||
|
//! # client_messages.push(client_blind_result.message);
|
||||||
|
//! # }
|
||||||
|
//! # use voprf::VoprfServer;
|
||||||
|
//! # let mut server_rng = OsRng;
|
||||||
|
//! # let server = VoprfServer::<CipherSuite>::new(&mut server_rng).unwrap();
|
||||||
|
//! # let VoprfServerBatchEvaluateResult { messages, proof } = server
|
||||||
|
//! # .batch_evaluate(&mut server_rng, &client_messages)
|
||||||
|
//! # .expect("Unable to perform server batch evaluate");
|
||||||
|
//! let client_batch_finalize_result = VoprfClient::batch_finalize(
|
||||||
|
//! &[b"input"; 10],
|
||||||
|
//! &client_states,
|
||||||
|
//! &messages,
|
||||||
|
//! &proof,
|
||||||
//! server.get_public_key(),
|
//! server.get_public_key(),
|
||||||
//! &Metadata::none(),
|
//! )
|
||||||
//! ).expect("Unable to perform client batch finalization");
|
//! .expect("Unable to perform client batch finalization")
|
||||||
|
//! .collect::<Vec<_>>();
|
||||||
//!
|
//!
|
||||||
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result.outputs);
|
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result);
|
||||||
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Metadata
|
//! ## Metadata
|
||||||
//!
|
//!
|
||||||
//! The optional metadata parameter included in the protocol allows clients and
|
//! The optional metadata parameter included in the POPRF mode allows clients
|
||||||
//! servers (of either mode) to cryptographically bind additional data to the
|
//! and servers to cryptographically bind additional data to the
|
||||||
//! VOPRF output. This metadata is known to both parties at the start of the protocol,
|
//! VOPRF output. This metadata is known to both parties at the start of the
|
||||||
//! and is inserted under the server's evaluate step and the client's finalize step.
|
//! protocol, and is inserted under the server's evaluate step and the client's
|
||||||
//! This metadata can be constructed with some type of higher-level domain separation
|
//! finalize step. This metadata can be constructed with some type of
|
||||||
//! to avoid cross-protocol attacks or related issues.
|
//! higher-level domain separation to avoid cross-protocol attacks or related
|
||||||
|
//! issues.
|
||||||
//!
|
//!
|
||||||
//! The default metadata simply consists of the empty vector of bytes, but a custom
|
//! The API for POPRF mode is similar to VOPRF mode, except that a [PoprfServer]
|
||||||
//! metadata can be specified, for example, by: `Metadata(b"custom metadata")`.
|
//! and [PoprfClient] are used, and that each of the functions accept an
|
||||||
|
//! additional (and optional) info parameter which represents the public input.
|
||||||
|
//! See <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-09.html#name-poprf-public-input>
|
||||||
|
//! for more detailed information on how this public input should be used.
|
||||||
//!
|
//!
|
||||||
//! # Features
|
//! # Features
|
||||||
//!
|
//!
|
||||||
//! - The `p256` feature enables using p256 as the underlying group for the [CipherSuite] choice.
|
//! - The `alloc` feature requires Rust's `alloc` crate and enables batching
|
||||||
//! Note that this is currently an experimental feature ⚠️, and is not yet ready for production use.
|
//! VOPRF evaluations.
|
||||||
//!
|
//!
|
||||||
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
|
//! - The `serde` feature, enabled by default, provides convenience functions
|
||||||
//! [serde](https://serde.rs/).
|
//! for serializing and deserializing with [serde](https://serde.rs/).
|
||||||
//!
|
//!
|
||||||
//! - The `u32_backend` and `u64_backend` features are re-exported from
|
//! - The `danger` feature, disabled by default, exposes functions for setting
|
||||||
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and allow for selecting
|
//! and getting internal values not available in the default API. These
|
||||||
//! the corresponding backend for the curve arithmetic used. The `u64_backend` feature is included as the default.
|
//! functions are intended for use in by higher-level cryptographic protocols
|
||||||
|
//! that need access to these raw values and are able to perform the necessary
|
||||||
|
//! validations on them (such as being valid group elements).
|
||||||
|
//!
|
||||||
|
//! - The `ristretto255-ciphersuite` features enables using [`Ristretto255`] as
|
||||||
|
//! a [`CipherSuite`].
|
||||||
|
//!
|
||||||
|
//! - The `ristretto255` feature enables using [`Ristretto255`] as the
|
||||||
|
//! underlying group for the [Group] choice. A backend feature, which are
|
||||||
|
//! re-exported from [curve25519-dalek] and allow for selecting the
|
||||||
|
//! corresponding backend for the curve arithmetic used, has to be selected,
|
||||||
|
//! otherwise compilation will fail. The `ristretto255-u64` feature is
|
||||||
|
//! included as the default. Other features are mapped as `ristretto255-u32`,
|
||||||
|
//! `ristretto255-fiat-u64` and `ristretto255-fiat-u32`. Any `ristretto255-*`
|
||||||
|
//! backend feature will enable the `ristretto255` feature.
|
||||||
|
//!
|
||||||
|
//! - The `ristretto255-simd` feature is re-exported from [curve25519-dalek] and
|
||||||
|
//! enables parallel formulas, using either AVX2 or AVX512-IFMA. This will
|
||||||
|
//! automatically enable the `ristretto255-u64` feature and requires Rust
|
||||||
|
//! nightly.
|
||||||
|
//!
|
||||||
|
//! [curve25519-dalek]: (https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features)
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
|
#![cfg_attr(not(test), deny(unsafe_code))]
|
||||||
#![deny(unsafe_code)]
|
#![no_std]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![warn(
|
||||||
|
clippy::cargo,
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
missing_debug_implementations,
|
||||||
|
missing_docs
|
||||||
|
)]
|
||||||
|
#![allow(clippy::multiple_crate_versions)]
|
||||||
|
|
||||||
|
#[cfg(any(feature = "alloc", test))]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
#[macro_use]
|
#[cfg(feature = "std")]
|
||||||
mod impls;
|
extern crate std;
|
||||||
#[macro_use]
|
|
||||||
mod serialization;
|
#[cfg(feature = "serde")]
|
||||||
|
extern crate serde_ as serde;
|
||||||
|
|
||||||
mod ciphersuite;
|
mod ciphersuite;
|
||||||
pub mod errors;
|
mod common;
|
||||||
pub mod group;
|
mod error;
|
||||||
pub mod hash;
|
mod group;
|
||||||
|
mod oprf;
|
||||||
|
mod poprf;
|
||||||
|
mod serialization;
|
||||||
mod voprf;
|
mod voprf;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -490,12 +499,32 @@ mod tests;
|
|||||||
|
|
||||||
// Exports
|
// Exports
|
||||||
|
|
||||||
pub use rand;
|
|
||||||
|
|
||||||
pub use crate::ciphersuite::CipherSuite;
|
pub use crate::ciphersuite::CipherSuite;
|
||||||
pub use crate::voprf::{
|
#[cfg(feature = "danger")]
|
||||||
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
|
pub use crate::common::derive_key;
|
||||||
NonVerifiableClientBlindResult, NonVerifiableClientFinalizeResult, NonVerifiableServer,
|
pub use crate::common::{
|
||||||
NonVerifiableServerEvaluateResult, VerifiableClient, VerifiableClientBlindResult,
|
BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof,
|
||||||
VerifiableClientFinalizeResult, VerifiableServer, VerifiableServerEvaluateResult,
|
};
|
||||||
|
pub use crate::error::{Error, InternalError, Result};
|
||||||
|
pub use crate::group::Group;
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
pub use crate::group::Ristretto255;
|
||||||
|
pub use crate::oprf::{OprfClient, OprfClientBlindResult, OprfServer};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub use crate::poprf::PoprfServerBatchEvaluateResult;
|
||||||
|
pub use crate::poprf::{
|
||||||
|
PoprfClient, PoprfClientBatchFinalizeResult, PoprfPreparedTweak, PoprfServer,
|
||||||
|
PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluateFinishedMessages,
|
||||||
|
PoprfServerBatchEvaluatePrepareResult, PoprfServerBatchEvaluatePreparedEvaluationElements,
|
||||||
|
};
|
||||||
|
pub use crate::serialization::{
|
||||||
|
BlindedElementLen, EvaluationElementLen, OprfClientLen, OprfServerLen, PoprfClientLen,
|
||||||
|
PoprfServerLen, ProofLen, VoprfClientLen, VoprfServerLen,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub use crate::voprf::VoprfServerBatchEvaluateResult;
|
||||||
|
pub use crate::voprf::{
|
||||||
|
VoprfClient, VoprfClientBatchFinalizeResult, VoprfClientBlindResult, VoprfServer,
|
||||||
|
VoprfServerBatchEvaluateFinishResult, VoprfServerBatchEvaluateFinishedMessages,
|
||||||
|
VoprfServerBatchEvaluatePreparedEvaluationElements, VoprfServerEvaluateResult,
|
||||||
};
|
};
|
||||||
|
|||||||
+406
@@ -0,0 +1,406 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
//! Contains the main OPRF API
|
||||||
|
|
||||||
|
use core::iter::{self, Map};
|
||||||
|
|
||||||
|
use derive_where::derive_where;
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::{Digest, Output, OutputSizeUser};
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
derive_key_internal, deterministic_blind_unchecked, i2osp_2, BlindedElement, EvaluationElement,
|
||||||
|
Mode, STR_FINALIZE,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use crate::serialization::serde::Scalar;
|
||||||
|
use crate::{CipherSuite, Error, Group, Result};
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Constants //
|
||||||
|
// ========= //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// High-level API Structs //
|
||||||
|
// ====================== //
|
||||||
|
////////////////////////////
|
||||||
|
|
||||||
|
/// A client which engages with a [OprfServer] in base mode, meaning
|
||||||
|
/// that the OPRF outputs are not verifiable.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct OprfClient<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) blind: <CS::Group as Group>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A server which engages with a [OprfClient] in base mode, meaning
|
||||||
|
/// that the OPRF outputs are not verifiable.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct OprfServer<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) sk: <CS::Group as Group>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// API Implementations //
|
||||||
|
// =================== //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> OprfClient<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Computes the first step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
|
||||||
|
pub fn blind<R: RngCore + CryptoRng>(
|
||||||
|
input: &[u8],
|
||||||
|
blinding_factor_rng: &mut R,
|
||||||
|
) -> Result<OprfClientBlindResult<CS>> {
|
||||||
|
let blind = CS::Group::random_scalar(blinding_factor_rng);
|
||||||
|
Self::deterministic_blind_unchecked_inner(input, blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the first step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF, taking a blinding factor scalar as input instead of sampling
|
||||||
|
/// from an RNG.
|
||||||
|
///
|
||||||
|
/// # Caution
|
||||||
|
///
|
||||||
|
/// This should be used with caution, since it does not perform any checks
|
||||||
|
/// on the validity of the blinding factor!
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
|
||||||
|
#[cfg(any(feature = "danger", test))]
|
||||||
|
pub fn deterministic_blind_unchecked(
|
||||||
|
input: &[u8],
|
||||||
|
blind: <CS::Group as Group>::Scalar,
|
||||||
|
) -> Result<OprfClientBlindResult<CS>> {
|
||||||
|
Self::deterministic_blind_unchecked_inner(input, blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Input`].
|
||||||
|
fn deterministic_blind_unchecked_inner(
|
||||||
|
input: &[u8],
|
||||||
|
blind: <CS::Group as Group>::Scalar,
|
||||||
|
) -> Result<OprfClientBlindResult<CS>> {
|
||||||
|
let blinded_element = deterministic_blind_unchecked::<CS>(input, &blind, Mode::Oprf)?;
|
||||||
|
Ok(OprfClientBlindResult {
|
||||||
|
state: Self { blind },
|
||||||
|
message: BlindedElement(blinded_element),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the third step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF, in which the client unblinds the server's message.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`].
|
||||||
|
pub fn finalize(
|
||||||
|
&self,
|
||||||
|
input: &[u8],
|
||||||
|
evaluation_element: &EvaluationElement<CS>,
|
||||||
|
) -> Result<Output<CS::Hash>> {
|
||||||
|
let unblinded_element = evaluation_element.0 * &CS::Group::invert_scalar(self.blind);
|
||||||
|
let mut outputs =
|
||||||
|
finalize_after_unblind::<CS, _, _>(iter::once((input, unblinded_element)), &[]);
|
||||||
|
outputs.next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only used for test functions
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn from_blind(blind: <CS::Group as Group>::Scalar) -> Self {
|
||||||
|
Self { blind }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the blind group element
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
|
||||||
|
self.blind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> OprfServer<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Produces a new instance of a [OprfServer] using a supplied RNG
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self> {
|
||||||
|
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
|
||||||
|
rng.fill_bytes(&mut seed);
|
||||||
|
Self::new_from_seed(&seed, &[])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a new instance of a [OprfServer] using a supplied set
|
||||||
|
/// of bytes to represent the server's private key
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if the private key is not a valid point on
|
||||||
|
/// the group or zero.
|
||||||
|
pub fn new_with_key(private_key_bytes: &[u8]) -> Result<Self> {
|
||||||
|
let sk = CS::Group::deserialize_scalar(private_key_bytes)?;
|
||||||
|
Ok(Self { sk })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a new instance of a [OprfServer] using a supplied set
|
||||||
|
/// of bytes which are used as a seed to derive the server's private key.
|
||||||
|
///
|
||||||
|
/// Corresponds to DeriveKeyPair() function from the VOPRF specification.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer
|
||||||
|
/// then `u16::MAX - 3`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
|
||||||
|
let sk = derive_key_internal::<CS>(seed, info, Mode::Oprf)?;
|
||||||
|
Ok(Self { sk })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for tests
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
|
||||||
|
self.sk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the second step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF. This message is sent from the server (who holds the OPRF key)
|
||||||
|
/// to the client.
|
||||||
|
pub fn evaluate(&self, blinded_element: &BlindedElement<CS>) -> EvaluationElement<CS> {
|
||||||
|
EvaluationElement(blinded_element.0 * &self.sk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Convenience Structs //
|
||||||
|
//==================== //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a non-verifiable client blind
|
||||||
|
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
pub struct OprfClientBlindResult<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// The state to be persisted on the client
|
||||||
|
pub state: OprfClient<CS>,
|
||||||
|
/// The message to send to the server
|
||||||
|
pub message: BlindedElement<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// Inner functions //
|
||||||
|
// =============== //
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
type FinalizeAfterUnblindResult<'a, C, I, IE> = Map<
|
||||||
|
IE,
|
||||||
|
fn((I, <<C as CipherSuite>::Group as Group>::Elem)) -> Result<Output<<C as CipherSuite>::Hash>>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Returned values can only fail with [`Error::Input`].
|
||||||
|
fn finalize_after_unblind<
|
||||||
|
'a,
|
||||||
|
CS: CipherSuite,
|
||||||
|
I: AsRef<[u8]>,
|
||||||
|
IE: 'a + Iterator<Item = (I, <CS::Group as Group>::Elem)>,
|
||||||
|
>(
|
||||||
|
inputs_and_unblinded_elements: IE,
|
||||||
|
_unused: &'a [u8],
|
||||||
|
) -> FinalizeAfterUnblindResult<CS, I, IE>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
inputs_and_unblinded_elements.map(|(input, unblinded_element)| {
|
||||||
|
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
|
||||||
|
|
||||||
|
// hashInput = I2OSP(len(input), 2) || input ||
|
||||||
|
// I2OSP(len(unblindedElement), 2) || unblindedElement ||
|
||||||
|
// "Finalize"
|
||||||
|
// return Hash(hashInput)
|
||||||
|
Ok(CS::Hash::new()
|
||||||
|
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
|
||||||
|
.chain_update(input.as_ref())
|
||||||
|
.chain_update(elem_len)
|
||||||
|
.chain_update(CS::Group::serialize_elem(unblinded_element))
|
||||||
|
.chain_update(&STR_FINALIZE)
|
||||||
|
.finalize())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Tests //
|
||||||
|
// ===== //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
use generic_array::sequence::Concat;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::common::{create_context_string, STR_HASH_TO_GROUP};
|
||||||
|
use crate::Group;
|
||||||
|
|
||||||
|
fn prf<CS: CipherSuite>(
|
||||||
|
input: &[u8],
|
||||||
|
key: <CS::Group as Group>::Scalar,
|
||||||
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
|
) -> Output<CS::Hash>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
|
||||||
|
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap();
|
||||||
|
|
||||||
|
let res = point * &key;
|
||||||
|
|
||||||
|
finalize_after_unblind::<CS, _, _>(iter::once((input, res)), info)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_retrieval<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
|
||||||
|
let server = OprfServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let message = server.evaluate(&client_blind_result.message);
|
||||||
|
let client_finalize_result = client_blind_result.state.finalize(input, &message).unwrap();
|
||||||
|
let res2 = prf::<CS>(input, server.get_private_key(), &[], Mode::Oprf);
|
||||||
|
assert_eq!(client_finalize_result, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_inversion_unsalted<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let mut input = [0u8; 64];
|
||||||
|
rng.fill_bytes(&mut input);
|
||||||
|
let client_blind_result = OprfClient::<CS>::blind(&input, &mut rng).unwrap();
|
||||||
|
let client_finalize_result = client_blind_result
|
||||||
|
.state
|
||||||
|
.finalize(&input, &EvaluationElement(client_blind_result.message.0))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dst =
|
||||||
|
GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(Mode::Oprf));
|
||||||
|
let point = CS::Group::hash_to_curve::<CS::Hash>(&[&input], &dst).unwrap();
|
||||||
|
let res2 = finalize_after_unblind::<CS, _, _>(iter::once((input.as_ref(), point)), &[])
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(client_finalize_result, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zeroize_oprf_client<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
|
||||||
|
|
||||||
|
let mut state = client_blind_result.state;
|
||||||
|
unsafe { ptr::drop_in_place(&mut state) };
|
||||||
|
assert!(state.serialize().iter().all(|&x| x == 0));
|
||||||
|
|
||||||
|
let mut message = client_blind_result.message;
|
||||||
|
unsafe { ptr::drop_in_place(&mut message) };
|
||||||
|
assert!(message.serialize().iter().all(|&x| x == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zeroize_oprf_server<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = OprfClient::<CS>::blind(input, &mut rng).unwrap();
|
||||||
|
let server = OprfServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let mut message = server.evaluate(&client_blind_result.message);
|
||||||
|
|
||||||
|
let mut state = server;
|
||||||
|
unsafe { ptr::drop_in_place(&mut state) };
|
||||||
|
assert!(state.serialize().iter().all(|&x| x == 0));
|
||||||
|
|
||||||
|
unsafe { ptr::drop_in_place(&mut message) };
|
||||||
|
assert!(message.serialize().iter().all(|&x| x == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_functionality() -> Result<()> {
|
||||||
|
use p256::NistP256;
|
||||||
|
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
{
|
||||||
|
use crate::Ristretto255;
|
||||||
|
|
||||||
|
base_retrieval::<Ristretto255>();
|
||||||
|
base_inversion_unsalted::<Ristretto255>();
|
||||||
|
|
||||||
|
zeroize_oprf_client::<Ristretto255>();
|
||||||
|
zeroize_oprf_server::<Ristretto255>();
|
||||||
|
}
|
||||||
|
|
||||||
|
base_retrieval::<NistP256>();
|
||||||
|
base_inversion_unsalted::<NistP256>();
|
||||||
|
|
||||||
|
zeroize_oprf_client::<NistP256>();
|
||||||
|
zeroize_oprf_server::<NistP256>();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+932
@@ -0,0 +1,932 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
//! Contains the main POPRF API
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::iter::{self, Map, Repeat, Zip};
|
||||||
|
|
||||||
|
use derive_where::derive_where;
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::{Digest, Output, OutputSizeUser};
|
||||||
|
use generic_array::sequence::Concat;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256};
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
create_context_string, derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2,
|
||||||
|
verify_proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof,
|
||||||
|
STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use crate::serialization::serde::{Element, Scalar};
|
||||||
|
use crate::{CipherSuite, Error, Group, Result};
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// High-level API Structs //
|
||||||
|
// ====================== //
|
||||||
|
////////////////////////////
|
||||||
|
|
||||||
|
/// A client which engages with a [PoprfServer] in verifiable mode, meaning
|
||||||
|
/// that the OPRF outputs can be checked against a server public key.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct PoprfClient<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) blind: <CS::Group as Group>::Scalar,
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
|
||||||
|
pub(crate) blinded_element: <CS::Group as Group>::Elem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A server which engages with a [PoprfClient] in verifiable mode, meaning
|
||||||
|
/// that the OPRF outputs can be checked against a server public key.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct PoprfServer<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
pub(crate) sk: <CS::Group as Group>::Scalar,
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
|
||||||
|
pub(crate) pk: <CS::Group as Group>::Elem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// API Implementations //
|
||||||
|
// =================== //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> PoprfClient<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Computes the first step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
|
||||||
|
pub fn blind<R: RngCore + CryptoRng>(
|
||||||
|
blinding_factor_rng: &mut R,
|
||||||
|
input: &[u8],
|
||||||
|
) -> Result<PoprfClientBlindResult<CS>> {
|
||||||
|
let blind = CS::Group::random_scalar(blinding_factor_rng);
|
||||||
|
Self::deterministic_blind_unchecked_inner(input, blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the first step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF, taking a blinding factor scalar as input instead of sampling
|
||||||
|
/// from an RNG.
|
||||||
|
///
|
||||||
|
/// # Caution
|
||||||
|
///
|
||||||
|
/// This should be used with caution, since it does not perform any checks
|
||||||
|
/// on the validity of the blinding factor!
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
|
||||||
|
#[cfg(any(feature = "danger", test))]
|
||||||
|
pub fn deterministic_blind_unchecked(
|
||||||
|
input: &[u8],
|
||||||
|
blind: <CS::Group as Group>::Scalar,
|
||||||
|
) -> Result<PoprfClientBlindResult<CS>> {
|
||||||
|
Self::deterministic_blind_unchecked_inner(input, blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Input`].
|
||||||
|
fn deterministic_blind_unchecked_inner(
|
||||||
|
input: &[u8],
|
||||||
|
blind: <CS::Group as Group>::Scalar,
|
||||||
|
) -> Result<PoprfClientBlindResult<CS>> {
|
||||||
|
let blinded_element = deterministic_blind_unchecked::<CS>(input, &blind, Mode::Poprf)?;
|
||||||
|
Ok(PoprfClientBlindResult {
|
||||||
|
state: Self {
|
||||||
|
blind,
|
||||||
|
blinded_element,
|
||||||
|
},
|
||||||
|
message: BlindedElement(blinded_element),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the third step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF, in which the client unblinds the server's message.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
|
||||||
|
/// - [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
/// - [`Error::ProofVerification`] if the `proof` failed to verify.
|
||||||
|
pub fn finalize(
|
||||||
|
&self,
|
||||||
|
input: &[u8],
|
||||||
|
evaluation_element: &EvaluationElement<CS>,
|
||||||
|
proof: &Proof<CS>,
|
||||||
|
pk: <CS::Group as Group>::Elem,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<Output<CS::Hash>> {
|
||||||
|
let clients = core::array::from_ref(self);
|
||||||
|
let messages = core::array::from_ref(evaluation_element);
|
||||||
|
|
||||||
|
let mut batch_result =
|
||||||
|
Self::batch_finalize(iter::once(input), clients, messages, proof, pk, info)?;
|
||||||
|
batch_result.next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for batching of the finalization of multiple [PoprfClient]
|
||||||
|
/// and [EvaluationElement] pairs
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
/// - [`Error::Batch`] if the number of `inputs`, `clients` and `messages`
|
||||||
|
/// don't match or is longer than [`u16::MAX`].
|
||||||
|
/// - [`Error::ProofVerification`] if the `proof` failed to verify.
|
||||||
|
///
|
||||||
|
/// The resulting messages can each fail individually with [`Error::Input`]
|
||||||
|
/// if the `input` is empty or longer than [`u16::MAX`].
|
||||||
|
pub fn batch_finalize<'a, II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator, IC, IM>(
|
||||||
|
inputs: II,
|
||||||
|
clients: &'a IC,
|
||||||
|
messages: &'a IM,
|
||||||
|
proof: &Proof<CS>,
|
||||||
|
pk: <CS::Group as Group>::Elem,
|
||||||
|
info: Option<&'a [u8]>,
|
||||||
|
) -> Result<PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM>>
|
||||||
|
where
|
||||||
|
CS: 'a,
|
||||||
|
&'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
|
||||||
|
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
|
||||||
|
<&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
{
|
||||||
|
let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?;
|
||||||
|
|
||||||
|
finalize_after_unblind::<'a, CS, _, _>(unblinded_elements, inputs, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only used for test functions
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
|
||||||
|
self.blind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> PoprfServer<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Produces a new instance of a [PoprfServer] using a supplied RNG
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self> {
|
||||||
|
let mut seed = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
|
||||||
|
rng.fill_bytes(&mut seed);
|
||||||
|
|
||||||
|
Self::new_from_seed(&seed, &[])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a new instance of a [PoprfServer] using a supplied set of
|
||||||
|
/// bytes to represent the server's private key
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if the private key is not a valid point on
|
||||||
|
/// the group or zero.
|
||||||
|
pub fn new_with_key(key: &[u8]) -> Result<Self> {
|
||||||
|
let sk = CS::Group::deserialize_scalar(key)?;
|
||||||
|
let pk = CS::Group::base_elem() * &sk;
|
||||||
|
Ok(Self { sk, pk })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a new instance of a [PoprfServer] using a supplied set of
|
||||||
|
/// bytes which are used as a seed to derive the server's private key.
|
||||||
|
///
|
||||||
|
/// Corresponds to DeriveKeyPair() function from the VOPRF specification.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer
|
||||||
|
/// then `u16::MAX - 3`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
|
||||||
|
let (sk, pk) = derive_keypair::<CS>(seed, info, Mode::Poprf)?;
|
||||||
|
Ok(Self { sk, pk })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for tests
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
|
||||||
|
self.sk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the second step for the multiplicative blinding version of
|
||||||
|
/// DH-OPRF. This message is sent from the server (who holds the OPRF key)
|
||||||
|
/// to the client.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn evaluate<R: RngCore + CryptoRng>(
|
||||||
|
&self,
|
||||||
|
rng: &mut R,
|
||||||
|
blinded_element: &BlindedElement<CS>,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<PoprfServerEvaluateResult<CS>> {
|
||||||
|
let PoprfServerBatchEvaluatePrepareResult {
|
||||||
|
mut prepared_evaluation_elements,
|
||||||
|
prepared_tweak,
|
||||||
|
} = self.batch_evaluate_prepare(iter::once(blinded_element), info)?;
|
||||||
|
|
||||||
|
let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap();
|
||||||
|
let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element);
|
||||||
|
|
||||||
|
let PoprfServerBatchEvaluateFinishResult {
|
||||||
|
mut messages,
|
||||||
|
proof,
|
||||||
|
} = Self::batch_evaluate_finish(
|
||||||
|
rng,
|
||||||
|
iter::once(blinded_element),
|
||||||
|
prepared_evaluation_elements,
|
||||||
|
&prepared_tweak,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(PoprfServerEvaluateResult {
|
||||||
|
message: messages.next().unwrap(),
|
||||||
|
proof,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for batching of the evaluation of multiple [BlindedElement]
|
||||||
|
/// messages from a [PoprfClient]
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub fn batch_evaluate<'a, R: RngCore + CryptoRng, IE>(
|
||||||
|
&self,
|
||||||
|
rng: &mut R,
|
||||||
|
blinded_elements: &'a IE,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<PoprfServerBatchEvaluateResult<CS>>
|
||||||
|
where
|
||||||
|
CS: 'a,
|
||||||
|
&'a IE: 'a + IntoIterator<Item = &'a BlindedElement<CS>>,
|
||||||
|
<&'a IE as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
{
|
||||||
|
let PoprfServerBatchEvaluatePrepareResult {
|
||||||
|
prepared_evaluation_elements,
|
||||||
|
prepared_tweak,
|
||||||
|
} = self.batch_evaluate_prepare(blinded_elements.into_iter(), info)?;
|
||||||
|
|
||||||
|
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
|
||||||
|
|
||||||
|
// This can't fail because we know the size of the inputs.
|
||||||
|
let PoprfServerBatchEvaluateFinishResult { messages, proof } =
|
||||||
|
Self::batch_evaluate_finish::<_, _, Vec<_>>(
|
||||||
|
rng,
|
||||||
|
blinded_elements.into_iter(),
|
||||||
|
&prepared_evaluation_elements,
|
||||||
|
&prepared_tweak,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let messages: Vec<_> = messages.collect();
|
||||||
|
|
||||||
|
Ok(PoprfServerBatchEvaluateResult { messages, proof })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alternative version of `batch_evaluate` without
|
||||||
|
/// memory allocation. Returned [`PreparedEvaluationElement`] have to
|
||||||
|
/// be [`collect`](Iterator::collect)ed and passed into
|
||||||
|
/// [`batch_evaluate_finish`](Self::batch_evaluate_finish).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
|
||||||
|
/// - [`Error::Protocol`] if the protocol fails and can't be completed.
|
||||||
|
pub fn batch_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>(
|
||||||
|
&self,
|
||||||
|
blinded_elements: I,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<PoprfServerBatchEvaluatePrepareResult<CS, I>>
|
||||||
|
where
|
||||||
|
CS: 'a,
|
||||||
|
{
|
||||||
|
let tweak = compute_tweak::<CS>(self.sk, info)?;
|
||||||
|
|
||||||
|
Ok(PoprfServerBatchEvaluatePrepareResult {
|
||||||
|
prepared_evaluation_elements: blinded_elements.zip(iter::repeat(tweak)).map(
|
||||||
|
|(blinded_element, tweak)| {
|
||||||
|
PreparedEvaluationElement(EvaluationElement(
|
||||||
|
blinded_element.0 * &CS::Group::invert_scalar(tweak),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
prepared_tweak: PoprfPreparedTweak(tweak),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`batch_evaluate_prepare`](Self::batch_evaluate_prepare) for more
|
||||||
|
/// details.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Batch`] if the number of `blinded_elements` and
|
||||||
|
/// `prepared_evaluation_elements` don't match or is longer then
|
||||||
|
/// [`u16::MAX`]
|
||||||
|
pub fn batch_evaluate_finish<
|
||||||
|
'a,
|
||||||
|
'b,
|
||||||
|
R: RngCore + CryptoRng,
|
||||||
|
IB: Iterator<Item = &'a BlindedElement<CS>> + ExactSizeIterator,
|
||||||
|
IE,
|
||||||
|
>(
|
||||||
|
rng: &mut R,
|
||||||
|
blinded_elements: IB,
|
||||||
|
prepared_evaluation_elements: &'b IE,
|
||||||
|
prepared_tweak: &PoprfPreparedTweak<CS>,
|
||||||
|
) -> Result<PoprfServerBatchEvaluateFinishResult<'b, CS, IE>>
|
||||||
|
where
|
||||||
|
CS: 'a,
|
||||||
|
&'b IE: IntoIterator<Item = &'b PreparedEvaluationElement<CS>>,
|
||||||
|
<&'b IE as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
{
|
||||||
|
let g = CS::Group::base_elem();
|
||||||
|
let tweak = prepared_tweak.0;
|
||||||
|
let tweaked_key = g * &tweak;
|
||||||
|
|
||||||
|
let proof = generate_proof(
|
||||||
|
rng,
|
||||||
|
tweak,
|
||||||
|
g,
|
||||||
|
tweaked_key,
|
||||||
|
prepared_evaluation_elements
|
||||||
|
.into_iter()
|
||||||
|
.map(|element| element.0 .0),
|
||||||
|
blinded_elements.map(|element| element.0),
|
||||||
|
Mode::Poprf,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let messages = prepared_evaluation_elements.into_iter().map(<fn(
|
||||||
|
&PreparedEvaluationElement<CS>,
|
||||||
|
) -> _>::from(
|
||||||
|
|element| EvaluationElement(element.0 .0),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(PoprfServerBatchEvaluateFinishResult { messages, proof })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the server's public key
|
||||||
|
pub fn get_public_key(&self) -> <CS::Group as Group>::Elem {
|
||||||
|
self.pk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> BlindedElement<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Creates a [BlindedElement] from a raw group element.
|
||||||
|
///
|
||||||
|
/// # Caution
|
||||||
|
///
|
||||||
|
/// This should be used with caution, since it does not perform any checks
|
||||||
|
/// on the validity of the value itself!
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the internal value
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn value(&self) -> <CS::Group as Group>::Elem {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> EvaluationElement<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Creates an [EvaluationElement] from a raw group element.
|
||||||
|
///
|
||||||
|
/// # Caution
|
||||||
|
///
|
||||||
|
/// This should be used with caution, since it does not perform any checks
|
||||||
|
/// on the validity of the value itself!
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the internal value
|
||||||
|
#[cfg(feature = "danger")]
|
||||||
|
pub fn value(&self) -> <CS::Group as Group>::Elem {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Convenience Structs //
|
||||||
|
//==================== //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a verifiable client blind
|
||||||
|
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
pub struct PoprfClientBlindResult<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// The state to be persisted on the client
|
||||||
|
pub state: PoprfClient<CS>,
|
||||||
|
/// The message to send to the server
|
||||||
|
pub message: BlindedElement<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concrete return type for [`PoprfClient::batch_finalize`].
|
||||||
|
pub type PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM> =
|
||||||
|
FinalizeAfterUnblindResult<'a, CS, PoprfUnblindResult<'a, CS, IC, IM>, II>;
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a verifiable server evaluate
|
||||||
|
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
pub struct PoprfServerEvaluateResult<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// The message to send to the client
|
||||||
|
pub message: EvaluationElement<CS>,
|
||||||
|
/// The proof for the client to verify
|
||||||
|
pub proof: Proof<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a verifiable server batch evaluate
|
||||||
|
#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub struct PoprfServerBatchEvaluateResult<CS: CipherSuite>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// The messages to send to the client
|
||||||
|
pub messages: Vec<EvaluationElement<CS>>,
|
||||||
|
/// The proof for the client to verify
|
||||||
|
pub proof: Proof<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concrete type of [`EvaluationElement`]s in
|
||||||
|
/// [`PoprfServerBatchEvaluatePrepareResult`].
|
||||||
|
pub type PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
|
||||||
|
Zip<I, Repeat<<<CS as CipherSuite>::Group as Group>::Scalar>>,
|
||||||
|
fn(
|
||||||
|
(
|
||||||
|
&BlindedElement<CS>,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Scalar,
|
||||||
|
),
|
||||||
|
) -> PreparedEvaluationElement<CS>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Prepared tweak by a partially verifiable server batch evaluate prepare.
|
||||||
|
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||||
|
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
|
serde(crate = "serde", bound = "")
|
||||||
|
)]
|
||||||
|
pub struct PoprfPreparedTweak<CS: CipherSuite>(
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
|
||||||
|
<CS::Group as Group>::Scalar,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>;
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a partially verifiable server batch
|
||||||
|
/// evaluate prepare
|
||||||
|
#[derive_where(Debug; I, <CS::Group as Group>::Scalar)]
|
||||||
|
pub struct PoprfServerBatchEvaluatePrepareResult<CS: CipherSuite, I>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Prepared [`EvaluationElement`].
|
||||||
|
pub prepared_evaluation_elements: PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I>,
|
||||||
|
/// Prepared tweak.
|
||||||
|
pub prepared_tweak: PoprfPreparedTweak<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concrete type of [`EvaluationElement`]s in
|
||||||
|
/// [`PoprfServerBatchEvaluateFinishResult`].
|
||||||
|
pub type PoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map<
|
||||||
|
<&'a I as IntoIterator>::IntoIter,
|
||||||
|
fn(&PreparedEvaluationElement<CS>) -> EvaluationElement<CS>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Contains the fields that are returned by a verifiable server batch evaluate
|
||||||
|
/// finish.
|
||||||
|
#[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, <CS::Group as Group>::Scalar)]
|
||||||
|
pub struct PoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
&'a I: IntoIterator<Item = &'a PreparedEvaluationElement<CS>>,
|
||||||
|
{
|
||||||
|
/// The [`EvaluationElement`]s to send to the client
|
||||||
|
pub messages: PoprfServerBatchEvaluateFinishedMessages<'a, CS, I>,
|
||||||
|
/// The proof for the client to verify
|
||||||
|
pub proof: Proof<CS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// Inner functions //
|
||||||
|
// =============== //
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/// Inner function for POPRF blind. Computes the tweaked key from the server
|
||||||
|
/// public key and info.
|
||||||
|
///
|
||||||
|
/// Can only fail with [`Error::Info`] or [`Error::Protocol`]
|
||||||
|
fn compute_tweaked_key<CS: CipherSuite>(
|
||||||
|
pk: <CS::Group as Group>::Elem,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<<CS::Group as Group>::Elem>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
// None for info is treated the same as empty bytes
|
||||||
|
let info = info.unwrap_or_default();
|
||||||
|
|
||||||
|
// framedInfo = "Info" || I2OSP(len(info), 2) || info
|
||||||
|
// m = G.HashToScalar(framedInfo)
|
||||||
|
// T = G.ScalarBaseMult(m)
|
||||||
|
// tweakedKey = T + pkS
|
||||||
|
// if tweakedKey == G.Identity():
|
||||||
|
// raise InvalidInputError
|
||||||
|
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
|
||||||
|
let framed_info = [STR_INFO.as_slice(), &info_len, info];
|
||||||
|
|
||||||
|
let dst =
|
||||||
|
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
|
||||||
|
// This can't fail, the size of the `input` is known.
|
||||||
|
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst).unwrap();
|
||||||
|
|
||||||
|
let t = CS::Group::base_elem() * &m;
|
||||||
|
let tweaked_key = t + &pk;
|
||||||
|
|
||||||
|
// Check if resulting element
|
||||||
|
match bool::from(CS::Group::is_identity_elem(tweaked_key)) {
|
||||||
|
true => Err(Error::Protocol),
|
||||||
|
false => Ok(tweaked_key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner function for POPRF evaluate. Computes the tweak from the server
|
||||||
|
/// private key and info.
|
||||||
|
///
|
||||||
|
/// Can only fail with [`Error::Info`] and [`Error::Protocol`].
|
||||||
|
fn compute_tweak<CS: CipherSuite>(
|
||||||
|
sk: <CS::Group as Group>::Scalar,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<<CS::Group as Group>::Scalar>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
// None for info is treated the same as empty bytes
|
||||||
|
let info = info.unwrap_or_default();
|
||||||
|
|
||||||
|
// framedInfo = "Info" || I2OSP(len(info), 2) || info
|
||||||
|
// m = G.HashToScalar(framedInfo)
|
||||||
|
// t = skS + m
|
||||||
|
// if t == 0:
|
||||||
|
// raise InverseError
|
||||||
|
let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
|
||||||
|
let framed_info = [STR_INFO.as_slice(), &info_len, info];
|
||||||
|
|
||||||
|
let dst =
|
||||||
|
GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::<CS>(Mode::Poprf));
|
||||||
|
// This can't fail, the size of the `input` is known.
|
||||||
|
let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst).unwrap();
|
||||||
|
|
||||||
|
let t = sk + &m;
|
||||||
|
|
||||||
|
// Check if resulting element is equal to zero
|
||||||
|
match bool::from(CS::Group::is_zero_scalar(t)) {
|
||||||
|
true => Err(Error::Protocol),
|
||||||
|
false => Ok(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoprfUnblindResult<'a, CS, IC, IM> = Map<
|
||||||
|
Zip<
|
||||||
|
Map<
|
||||||
|
<&'a IC as IntoIterator>::IntoIter,
|
||||||
|
fn(&PoprfClient<CS>) -> <<CS as CipherSuite>::Group as Group>::Scalar,
|
||||||
|
>,
|
||||||
|
<&'a IM as IntoIterator>::IntoIter,
|
||||||
|
>,
|
||||||
|
fn(
|
||||||
|
(
|
||||||
|
<<CS as CipherSuite>::Group as Group>::Scalar,
|
||||||
|
&'a EvaluationElement<CS>,
|
||||||
|
),
|
||||||
|
) -> <<CS as CipherSuite>::Group as Group>::Elem,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Info`], [`Error::Protocol`], [`Error::Batch] or
|
||||||
|
/// [`Error::ProofVerification`].
|
||||||
|
fn poprf_unblind<'a, CS: 'a + CipherSuite, IC, IM>(
|
||||||
|
clients: &'a IC,
|
||||||
|
messages: &'a IM,
|
||||||
|
pk: <CS::Group as Group>::Elem,
|
||||||
|
proof: &Proof<CS>,
|
||||||
|
info: Option<&[u8]>,
|
||||||
|
) -> Result<PoprfUnblindResult<'a, CS, IC, IM>>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
&'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
|
||||||
|
<&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
&'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
|
||||||
|
<&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
{
|
||||||
|
let info = info.unwrap_or_default();
|
||||||
|
let tweaked_key = compute_tweaked_key::<CS>(pk, Some(info))?;
|
||||||
|
|
||||||
|
let g = CS::Group::base_elem();
|
||||||
|
|
||||||
|
let blinds = clients
|
||||||
|
.into_iter()
|
||||||
|
// Convert to `fn` pointer to make a return type possible.
|
||||||
|
.map(<fn(&PoprfClient<CS>) -> _>::from(|x| x.blind));
|
||||||
|
let evaluation_elements = messages.into_iter().map(|element| element.0);
|
||||||
|
let blinded_elements = clients.into_iter().map(|client| client.blinded_element);
|
||||||
|
|
||||||
|
verify_proof(
|
||||||
|
g,
|
||||||
|
tweaked_key,
|
||||||
|
evaluation_elements,
|
||||||
|
blinded_elements,
|
||||||
|
proof,
|
||||||
|
Mode::Poprf,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(blinds
|
||||||
|
.zip(messages.into_iter())
|
||||||
|
.map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalizeAfterUnblindResult<'a, CS, IE, II> = Map<
|
||||||
|
Zip<Zip<IE, II>, Repeat<&'a [u8]>>,
|
||||||
|
fn(
|
||||||
|
((<<CS as CipherSuite>::Group as Group>::Elem, &[u8]), &[u8]),
|
||||||
|
) -> Result<GenericArray<u8, <<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize>>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Can only fail with [`Error::Batch`] and returned values can only fail with
|
||||||
|
/// [`Error::Info`] or [`Error::Input`] individually.
|
||||||
|
fn finalize_after_unblind<
|
||||||
|
'a,
|
||||||
|
CS: CipherSuite,
|
||||||
|
IE: 'a + Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
|
||||||
|
II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator,
|
||||||
|
>(
|
||||||
|
unblinded_elements: IE,
|
||||||
|
inputs: II,
|
||||||
|
info: Option<&'a [u8]>,
|
||||||
|
) -> Result<FinalizeAfterUnblindResult<'a, CS, IE, II>>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
if unblinded_elements.len() != inputs.len() {
|
||||||
|
return Err(Error::Batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = info.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(unblinded_elements.zip(inputs).zip(iter::repeat(info)).map(
|
||||||
|
|((unblinded_element, input), info)| {
|
||||||
|
let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
|
||||||
|
|
||||||
|
// hashInput = I2OSP(len(input), 2) || input ||
|
||||||
|
// I2OSP(len(info), 2) || info ||
|
||||||
|
// I2OSP(len(unblindedElement), 2) || unblindedElement ||
|
||||||
|
// "Finalize"
|
||||||
|
// return Hash(hashInput)
|
||||||
|
let output = CS::Hash::new()
|
||||||
|
.chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
|
||||||
|
.chain_update(input.as_ref())
|
||||||
|
.chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Info)?)
|
||||||
|
.chain_update(info.as_ref())
|
||||||
|
.chain_update(elem_len)
|
||||||
|
.chain_update(CS::Group::serialize_elem(unblinded_element))
|
||||||
|
.chain_update(STR_FINALIZE)
|
||||||
|
.finalize();
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Tests //
|
||||||
|
// ===== //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use core::ops::Add;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
use generic_array::typenum::Sum;
|
||||||
|
use generic_array::ArrayLength;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::common::STR_HASH_TO_GROUP;
|
||||||
|
use crate::Group;
|
||||||
|
|
||||||
|
fn prf<CS: CipherSuite>(
|
||||||
|
input: &[u8],
|
||||||
|
key: <CS::Group as Group>::Scalar,
|
||||||
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
|
) -> Output<CS::Hash>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let t = compute_tweak::<CS>(key, Some(info)).unwrap();
|
||||||
|
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::<CS>(mode));
|
||||||
|
let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst).unwrap();
|
||||||
|
|
||||||
|
// evaluatedElement = G.ScalarInverse(t) * blindedElement
|
||||||
|
let res = point * &CS::Group::invert_scalar(t);
|
||||||
|
|
||||||
|
finalize_after_unblind::<CS, _, _>(iter::once(res), iter::once(input), Some(info))
|
||||||
|
.unwrap()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verifiable_retrieval<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.evaluate(&mut rng, &client_blind_result.message, Some(info))
|
||||||
|
.unwrap();
|
||||||
|
let client_finalize_result = client_blind_result
|
||||||
|
.state
|
||||||
|
.finalize(
|
||||||
|
input,
|
||||||
|
&server_result.message,
|
||||||
|
&server_result.proof,
|
||||||
|
server.get_public_key(),
|
||||||
|
Some(info),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let res2 = prf::<CS>(input, server.get_private_key(), info, Mode::Poprf);
|
||||||
|
assert_eq!(client_finalize_result, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verifiable_bad_public_key<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.evaluate(&mut rng, &client_blind_result.message, Some(info))
|
||||||
|
.unwrap();
|
||||||
|
let wrong_pk = {
|
||||||
|
let dst = GenericArray::from(STR_HASH_TO_GROUP)
|
||||||
|
.concat(create_context_string::<CS>(Mode::Oprf));
|
||||||
|
// Choose a group element that is unlikely to be the right public key
|
||||||
|
CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst).unwrap()
|
||||||
|
};
|
||||||
|
let client_finalize_result = client_blind_result.state.finalize(
|
||||||
|
input,
|
||||||
|
&server_result.message,
|
||||||
|
&server_result.proof,
|
||||||
|
wrong_pk,
|
||||||
|
Some(info),
|
||||||
|
);
|
||||||
|
assert!(client_finalize_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zeroize_verifiable_client<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
|
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
|
||||||
|
|
||||||
|
let mut state = client_blind_result.state;
|
||||||
|
unsafe { ptr::drop_in_place(&mut state) };
|
||||||
|
assert!(state.serialize().iter().all(|&x| x == 0));
|
||||||
|
|
||||||
|
let mut message = client_blind_result.message;
|
||||||
|
unsafe { ptr::drop_in_place(&mut message) };
|
||||||
|
assert!(message.serialize().iter().all(|&x| x == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zeroize_verifiable_server<CS: CipherSuite>()
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
|
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ElemLen>: ArrayLength<u8>,
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
|
||||||
|
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
let input = b"input";
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server = PoprfServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let client_blind_result = PoprfClient::<CS>::blind(&mut rng, input).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.evaluate(&mut rng, &client_blind_result.message, Some(info))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut state = server;
|
||||||
|
unsafe { ptr::drop_in_place(&mut state) };
|
||||||
|
assert!(state.serialize().iter().all(|&x| x == 0));
|
||||||
|
|
||||||
|
let mut message = server_result.message;
|
||||||
|
unsafe { ptr::drop_in_place(&mut message) };
|
||||||
|
assert!(message.serialize().iter().all(|&x| x == 0));
|
||||||
|
|
||||||
|
let mut proof = server_result.proof;
|
||||||
|
unsafe { ptr::drop_in_place(&mut proof) };
|
||||||
|
assert!(proof.serialize().iter().all(|&x| x == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_functionality() -> Result<()> {
|
||||||
|
use p256::NistP256;
|
||||||
|
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
{
|
||||||
|
use crate::Ristretto255;
|
||||||
|
|
||||||
|
verifiable_retrieval::<Ristretto255>();
|
||||||
|
verifiable_bad_public_key::<Ristretto255>();
|
||||||
|
|
||||||
|
zeroize_verifiable_client::<Ristretto255>();
|
||||||
|
zeroize_verifiable_server::<Ristretto255>();
|
||||||
|
}
|
||||||
|
|
||||||
|
verifiable_retrieval::<NistP256>();
|
||||||
|
verifiable_bad_public_key::<NistP256>();
|
||||||
|
|
||||||
|
zeroize_verifiable_client::<NistP256>();
|
||||||
|
zeroize_verifiable_server::<NistP256>();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+368
-154
@@ -5,224 +5,438 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
//! Handles the serialization of each of the components used
|
//! Handles the serialization of each of the components used in the VOPRF
|
||||||
//! in the VOPRF protocol
|
//! protocol
|
||||||
|
|
||||||
|
use core::ops::Add;
|
||||||
|
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::OutputSizeUser;
|
||||||
|
use generic_array::sequence::Concat;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, Unsigned, U256};
|
||||||
|
use generic_array::{ArrayLength, GenericArray};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ciphersuite::CipherSuite,
|
BlindedElement, CipherSuite, Error, EvaluationElement, Group, OprfClient, OprfServer,
|
||||||
errors::InternalError,
|
PoprfClient, PoprfServer, Proof, Result, VoprfClient, VoprfServer,
|
||||||
group::Group,
|
|
||||||
voprf::{
|
|
||||||
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
|
|
||||||
VerifiableClient, VerifiableServer,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
|
||||||
use generic_array::{typenum::Unsigned, GenericArray};
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////
|
||||||
// Serialization and Deserialization for High-Level API //
|
// Serialization and Deserialization for High-Level API //
|
||||||
// ==================================================== //
|
// ==================================================== //
|
||||||
//////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
impl<CS: CipherSuite> NonVerifiableClient<CS> {
|
/// Length of [`OprfClient`] in bytes for serialization.
|
||||||
|
pub type OprfClientLen<CS> = <<CS as CipherSuite>::Group as Group>::ScalarLen;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> OprfClient<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
/// Serialization into bytes
|
/// Serialization into bytes
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> GenericArray<u8, OprfClientLen<CS>> {
|
||||||
[
|
CS::Group::serialize_scalar(self.blind)
|
||||||
CS::Group::scalar_as_bytes(self.blind).to_vec(),
|
|
||||||
self.data.clone(),
|
|
||||||
]
|
|
||||||
.concat()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
/// Deserialization from bytes
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
///
|
||||||
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
|
/// # Errors
|
||||||
if input.len() < scalar_len {
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
return Err(InternalError::SizeError);
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
}
|
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
let blind = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
|
Ok(Self { blind })
|
||||||
let data = input[scalar_len..].to_vec();
|
|
||||||
|
|
||||||
Ok(Self { blind, data })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> VerifiableClient<CS> {
|
/// Length of [`VoprfClient`] in bytes for serialization.
|
||||||
|
pub type VoprfClientLen<CS> = Sum<
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ElemLen,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> VoprfClient<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
/// Serialization into bytes
|
/// Serialization into bytes
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> GenericArray<u8, VoprfClientLen<CS>>
|
||||||
[
|
where
|
||||||
CS::Group::scalar_as_bytes(self.blind).to_vec(),
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
self.blinded_element.to_arr().to_vec(),
|
VoprfClientLen<CS>: ArrayLength<u8>,
|
||||||
self.data.clone(),
|
{
|
||||||
]
|
<CS::Group as Group>::serialize_scalar(self.blind)
|
||||||
.concat()
|
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
/// Deserialization from bytes
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
///
|
||||||
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
|
/// # Errors
|
||||||
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
if input.len() < scalar_len + elem_len {
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
return Err(InternalError::SizeError);
|
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
}
|
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
let blind = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
|
|
||||||
let blinded_element = CS::Group::from_element_slice(GenericArray::from_slice(
|
|
||||||
&input[scalar_len..scalar_len + elem_len],
|
|
||||||
))?;
|
|
||||||
let data = input[scalar_len + elem_len..].to_vec();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
blind,
|
blind,
|
||||||
blinded_element,
|
blinded_element,
|
||||||
data,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> NonVerifiableServer<CS> {
|
/// Length of [`PoprfClient`] in bytes for serialization.
|
||||||
|
pub type PoprfClientLen<CS> = Sum<
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ElemLen,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> PoprfClient<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
/// Serialization into bytes
|
/// Serialization into bytes
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> GenericArray<u8, PoprfClientLen<CS>>
|
||||||
CS::Group::scalar_as_bytes(self.sk).to_vec()
|
where
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
|
PoprfClientLen<CS>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
<CS::Group as Group>::serialize_scalar(self.blind)
|
||||||
|
.concat(<CS::Group as Group>::serialize_elem(self.blinded_element))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
/// Deserialization from bytes
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
///
|
||||||
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
|
/// # Errors
|
||||||
if input.len() != scalar_len {
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
return Err(InternalError::SizeError);
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
}
|
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(input))?;
|
Ok(Self {
|
||||||
|
blind,
|
||||||
|
blinded_element,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Length of [`OprfServer`] in bytes for serialization.
|
||||||
|
pub type OprfServerLen<CS> = <<CS as CipherSuite>::Group as Group>::ScalarLen;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> OprfServer<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Serialization into bytes
|
||||||
|
pub fn serialize(&self) -> GenericArray<u8, OprfServerLen<CS>> {
|
||||||
|
CS::Group::serialize_scalar(self.sk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization from bytes
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
|
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
Ok(Self { sk })
|
Ok(Self { sk })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> VerifiableServer<CS> {
|
/// Length of [`VoprfServer`] in bytes for serialization.
|
||||||
|
pub type VoprfServerLen<CS> = Sum<
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ElemLen,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> VoprfServer<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
/// Serialization into bytes
|
/// Serialization into bytes
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> GenericArray<u8, VoprfServerLen<CS>>
|
||||||
[
|
where
|
||||||
CS::Group::scalar_as_bytes(self.sk).to_vec(),
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
self.pk.to_arr().to_vec(),
|
VoprfServerLen<CS>: ArrayLength<u8>,
|
||||||
]
|
{
|
||||||
.concat()
|
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
/// Deserialization from bytes
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
///
|
||||||
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
|
/// # Errors
|
||||||
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
if input.len() != scalar_len + elem_len {
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
return Err(InternalError::SizeError);
|
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
}
|
let pk = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
|
|
||||||
let pk = CS::Group::from_element_slice(GenericArray::from_slice(&input[scalar_len..]))?;
|
|
||||||
|
|
||||||
Ok(Self { sk, pk })
|
Ok(Self { sk, pk })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> Proof<CS> {
|
/// Length of [`PoprfServer`] in bytes for serialization.
|
||||||
|
pub type PoprfServerLen<CS> = Sum<
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ElemLen,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> PoprfServer<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
/// Serialization into bytes
|
/// Serialization into bytes
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> GenericArray<u8, PoprfServerLen<CS>>
|
||||||
[
|
where
|
||||||
CS::Group::scalar_as_bytes(self.c_scalar),
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ElemLen>,
|
||||||
CS::Group::scalar_as_bytes(self.s_scalar),
|
PoprfServerLen<CS>: ArrayLength<u8>,
|
||||||
]
|
{
|
||||||
.concat()
|
CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
/// Deserialization from bytes
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
///
|
||||||
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
|
/// # Errors
|
||||||
if input.len() < scalar_len + scalar_len {
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
return Err(InternalError::SizeError);
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
|
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
let pk = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
|
Ok(Self { sk, pk })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Length of [`Proof`] in bytes for serialization.
|
||||||
|
pub type ProofLen<CS> = Sum<
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
<<CS as CipherSuite>::Group as Group>::ScalarLen,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> Proof<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Serialization into bytes
|
||||||
|
pub fn serialize(&self) -> GenericArray<u8, ProofLen<CS>>
|
||||||
|
where
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
|
||||||
|
ProofLen<CS>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
CS::Group::serialize_scalar(self.c_scalar)
|
||||||
|
.concat(CS::Group::serialize_scalar(self.s_scalar))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization from bytes
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
|
let c_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
let s_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
|
Ok(Proof { c_scalar, s_scalar })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Length of [`BlindedElement`] in bytes for serialization.
|
||||||
|
pub type BlindedElementLen<CS> = <<CS as CipherSuite>::Group as Group>::ElemLen;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> BlindedElement<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Serialization into bytes
|
||||||
|
pub fn serialize(&self) -> GenericArray<u8, BlindedElementLen<CS>> {
|
||||||
|
CS::Group::serialize_elem(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization from bytes
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
|
let value = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Length of [`EvaluationElement`] in bytes for serialization.
|
||||||
|
pub type EvaluationElementLen<CS> = <<CS as CipherSuite>::Group as Group>::ElemLen;
|
||||||
|
|
||||||
|
impl<CS: CipherSuite> EvaluationElement<CS>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
/// Serialization into bytes
|
||||||
|
pub fn serialize(&self) -> GenericArray<u8, EvaluationElementLen<CS>> {
|
||||||
|
CS::Group::serialize_elem(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization from bytes
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// [`Error::Deserialization`] if failed to deserialize `input`.
|
||||||
|
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||||
|
let value = deserialize_elem::<CS::Group>(&mut input)?;
|
||||||
|
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_elem<G: Group>(input: &mut &[u8]) -> Result<G::Elem> {
|
||||||
|
let input = input
|
||||||
|
.take_ext(G::ElemLen::USIZE)
|
||||||
|
.ok_or(Error::Deserialization)?;
|
||||||
|
G::deserialize_elem(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_scalar<G: Group>(input: &mut &[u8]) -> Result<G::Scalar> {
|
||||||
|
let input = input
|
||||||
|
.take_ext(G::ScalarLen::USIZE)
|
||||||
|
.ok_or(Error::Deserialization)?;
|
||||||
|
G::deserialize_scalar(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SliceExt {
|
||||||
|
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SliceExt for [T] {
|
||||||
|
fn take_ext(self: &mut &Self, take: usize) -> Option<&Self> {
|
||||||
|
if take > self.len() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
Ok(Proof {
|
|
||||||
c_scalar: CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?,
|
let (front, back) = self.split_at(take);
|
||||||
s_scalar: CS::Group::from_scalar_slice(GenericArray::from_slice(&input[scalar_len..]))?,
|
*self = back;
|
||||||
})
|
Some(front)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> BlindedElement<CS> {
|
#[cfg(feature = "serde")]
|
||||||
/// Serialization into bytes
|
pub(crate) mod serde {
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
use core::marker::PhantomData;
|
||||||
self.value.to_arr().to_vec()
|
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
use serde::de::{Deserializer, Error};
|
||||||
|
use serde::ser::Serializer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Group;
|
||||||
|
|
||||||
|
pub(crate) struct Element<G: Group>(PhantomData<G>);
|
||||||
|
|
||||||
|
impl<'de, G: Group> Element<G> {
|
||||||
|
pub(crate) fn deserialize<D>(deserializer: D) -> Result<G::Elem, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
GenericArray::<_, G::ElemLen>::deserialize(deserializer)
|
||||||
|
.and_then(|bytes| G::deserialize_elem(&bytes).map_err(D::Error::custom))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn serialize<S>(self_: &G::Elem, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
G::serialize_elem(*self_).serialize(serializer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization from bytes
|
pub(crate) struct Scalar<G: Group>(PhantomData<G>);
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
|
||||||
Ok(Self {
|
impl<'de, G: Group> Scalar<G> {
|
||||||
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
|
pub(crate) fn deserialize<D>(deserializer: D) -> Result<G::Scalar, D::Error>
|
||||||
})
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
GenericArray::<_, G::ScalarLen>::deserialize(deserializer)
|
||||||
|
.and_then(|bytes| G::deserialize_scalar(&bytes).map_err(D::Error::custom))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn serialize<S>(self_: &G::Scalar, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
G::serialize_scalar(*self_).serialize(serializer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CS: CipherSuite> EvaluationElement<CS> {
|
|
||||||
/// Serialization into bytes
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
|
||||||
self.value.to_arr().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialization from bytes
|
|
||||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
|
||||||
Ok(Self {
|
|
||||||
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////
|
|
||||||
// Helper Functions //
|
|
||||||
// ================ //
|
|
||||||
//////////////////////
|
|
||||||
|
|
||||||
// Corresponds to the I2OSP() function from RFC8017
|
|
||||||
pub(crate) fn i2osp(input: usize, length: usize) -> Result<alloc::vec::Vec<u8>, InternalError> {
|
|
||||||
let sizeof_usize = core::mem::size_of::<usize>();
|
|
||||||
|
|
||||||
// Check if input >= 256^length
|
|
||||||
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
|
|
||||||
return Err(InternalError::SerializationError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if length <= sizeof_usize {
|
|
||||||
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut output = alloc::vec![0u8; length];
|
|
||||||
output.splice(
|
|
||||||
length - sizeof_usize..length,
|
|
||||||
input.to_be_bytes().iter().cloned(),
|
|
||||||
);
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes I2OSP(len(input), max_bytes) || input
|
|
||||||
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, InternalError> {
|
|
||||||
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod test {
|
||||||
use super::*;
|
use proptest::collection::vec;
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
// Test the error condition for I2OSP
|
use crate::{
|
||||||
#[test]
|
BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof,
|
||||||
fn test_i2osp_err_check() {
|
VoprfClient, VoprfServer,
|
||||||
assert!(i2osp(0, 1).is_ok());
|
};
|
||||||
|
|
||||||
assert!(i2osp(255, 1).is_ok());
|
macro_rules! test_deserialize {
|
||||||
assert!(i2osp(256, 1).is_err());
|
($item:ident, $bytes:ident) => {
|
||||||
assert!(i2osp(257, 1).is_err());
|
#[cfg(feature = "ristretto255")]
|
||||||
|
{
|
||||||
|
let _ = $item::<crate::Ristretto255>::deserialize(&$bytes[..]);
|
||||||
|
}
|
||||||
|
|
||||||
assert!(i2osp(256 * 256 - 1, 2).is_ok());
|
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
|
||||||
assert!(i2osp(256 * 256, 2).is_err());
|
};
|
||||||
assert!(i2osp(256 * 256 + 1, 2).is_err());
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_oprf_client(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(OprfClient, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_voprf_client(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(VoprfClient, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_poprf_client(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(PoprfClient, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_oprf_server(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(OprfServer, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_voprf_server(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(VoprfServer, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_poprf_server(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(PoprfServer, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_blinded_element(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(BlindedElement, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_evaluation_element(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(EvaluationElement, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nocrash_proof(bytes in vec(any::<u8>(), 0..200)) {
|
||||||
|
test_deserialize!(Proof, bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,21 +7,20 @@
|
|||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::cmp::min;
|
use core::cmp::min;
|
||||||
use rand::{CryptoRng, Error, RngCore};
|
|
||||||
|
use rand_core::{CryptoRng, Error, RngCore};
|
||||||
|
|
||||||
/// A simple implementation of `RngCore` for testing purposes.
|
/// A simple implementation of `RngCore` for testing purposes.
|
||||||
///
|
///
|
||||||
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
|
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
|
||||||
///
|
|
||||||
///
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CycleRng {
|
pub struct CycleRng {
|
||||||
v: Vec<u8>,
|
v: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CycleRng {
|
impl CycleRng {
|
||||||
/// Create a `CycleRng`, yielding a sequence starting with
|
/// Create a `CycleRng`, yielding a sequence starting with `initial` and
|
||||||
/// `initial` and looping thereafter
|
/// looping thereafter
|
||||||
pub fn new(initial: Vec<u8>) -> Self {
|
pub fn new(initial: Vec<u8>) -> Self {
|
||||||
CycleRng { v: initial }
|
CycleRng { v: initial }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-17
@@ -5,22 +5,7 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
|
mod cfrg_vectors;
|
||||||
mod mock_rng;
|
mod mock_rng;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod voprf_test_vectors;
|
mod test_cfrg_vectors;
|
||||||
mod voprf_vectors;
|
|
||||||
|
|
||||||
/// Ciphersuite definitions for tests
|
|
||||||
pub(crate) struct Ristretto255Sha512;
|
|
||||||
impl crate::CipherSuite for Ristretto255Sha512 {
|
|
||||||
type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
|
||||||
type Hash = sha2::Sha512;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "p256")]
|
|
||||||
pub(crate) struct P256Sha256;
|
|
||||||
#[cfg(feature = "p256")]
|
|
||||||
impl crate::CipherSuite for P256Sha256 {
|
|
||||||
type Group = p256_::ProjectivePoint;
|
|
||||||
type Hash = sha2::Sha256;
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-6
@@ -5,7 +5,9 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
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 {
|
pub(crate) fn rfc_to_json(input: &str) -> String {
|
||||||
format!("{{\n{}\n}}", parse_ciphersuites(input))
|
format!("{{\n{}\n}}", parse_ciphersuites(input))
|
||||||
@@ -20,7 +22,7 @@ fn parse_ciphersuites(input: &str) -> String {
|
|||||||
for caps in re.captures_iter(input) {
|
for caps in re.captures_iter(input) {
|
||||||
let ciphersuite = format!(
|
let ciphersuite = format!(
|
||||||
"\"{}\": {{ {} }}",
|
"\"{}\": {{ {} }}",
|
||||||
caps["ciphersuite"].to_string(),
|
&caps["ciphersuite"],
|
||||||
parse_modes(chunks[count])
|
parse_modes(chunks[count])
|
||||||
);
|
);
|
||||||
ciphersuites.push(ciphersuite);
|
ciphersuites.push(ciphersuite);
|
||||||
@@ -39,7 +41,7 @@ fn parse_modes(input: &str) -> String {
|
|||||||
for caps in re.captures_iter(input) {
|
for caps in re.captures_iter(input) {
|
||||||
let mode = format!(
|
let mode = format!(
|
||||||
"\"{}\": [\n {} \n]",
|
"\"{}\": [\n {} \n]",
|
||||||
caps["mode"].to_string(),
|
&caps["mode"],
|
||||||
parse_vectors(chunks[count])
|
parse_vectors(chunks[count])
|
||||||
);
|
);
|
||||||
modes.push(mode);
|
modes.push(mode);
|
||||||
@@ -85,7 +87,7 @@ fn parse_params(input: &str) -> String {
|
|||||||
// If line contains =, then
|
// If line contains =, then
|
||||||
if line.contains('=') {
|
if line.contains('=') {
|
||||||
// Clear out any existing string and flush to params
|
// Clear out any existing string and flush to params
|
||||||
if param.len() > 0 {
|
if !param.is_empty() {
|
||||||
param += "\"";
|
param += "\"";
|
||||||
params.push(param);
|
params.push(param);
|
||||||
}
|
}
|
||||||
@@ -97,11 +99,11 @@ fn parse_params(input: &str) -> String {
|
|||||||
param = format!(" \"{}\": \"{}", key, val);
|
param = format!(" \"{}\": \"{}", key, val);
|
||||||
} else {
|
} else {
|
||||||
let s = line.trim().to_string();
|
let s = line.trim().to_string();
|
||||||
if s.contains("~") || s.contains("#") {
|
if s.contains('~') || s.contains('#') {
|
||||||
// Ignore comment lines
|
// Ignore comment lines
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if s.len() > 0 {
|
if !s.is_empty() {
|
||||||
param += &s;
|
param += &s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,478 @@
|
|||||||
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
//
|
||||||
|
// This source code is licensed under both the MIT license found in the
|
||||||
|
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
||||||
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
|
// of this source tree.
|
||||||
|
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::ops::Add;
|
||||||
|
|
||||||
|
use digest::core_api::BlockSizeUser;
|
||||||
|
use digest::OutputSizeUser;
|
||||||
|
use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256};
|
||||||
|
use generic_array::ArrayLength;
|
||||||
|
use json::JsonValue;
|
||||||
|
|
||||||
|
use crate::tests::mock_rng::CycleRng;
|
||||||
|
use crate::tests::parser::*;
|
||||||
|
use crate::{
|
||||||
|
BlindedElement, CipherSuite, EvaluationElement, Group, OprfClient, OprfServer, PoprfClient,
|
||||||
|
PoprfServer, PoprfServerBatchEvaluateFinishResult, PoprfServerBatchEvaluatePrepareResult,
|
||||||
|
Proof, Result, VoprfClient, VoprfServer, VoprfServerBatchEvaluateFinishResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct VOPRFTestVectorParameters {
|
||||||
|
seed: Vec<u8>,
|
||||||
|
sksm: Vec<u8>,
|
||||||
|
pksm: Vec<u8>,
|
||||||
|
input: Vec<Vec<u8>>,
|
||||||
|
info: Vec<u8>,
|
||||||
|
key_info: Vec<u8>,
|
||||||
|
blind: Vec<Vec<u8>>,
|
||||||
|
blinded_element: Vec<Vec<u8>>,
|
||||||
|
evaluation_element: Vec<Vec<u8>>,
|
||||||
|
proof: Vec<u8>,
|
||||||
|
proof_random_scalar: Vec<u8>,
|
||||||
|
output: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
|
||||||
|
VOPRFTestVectorParameters {
|
||||||
|
seed: decode(values, "Seed"),
|
||||||
|
sksm: decode(values, "skSm"),
|
||||||
|
pksm: decode(values, "pkSm"),
|
||||||
|
input: decode_vec(values, "Input"),
|
||||||
|
info: decode(values, "Info"),
|
||||||
|
key_info: decode(values, "KeyInfo"),
|
||||||
|
blind: decode_vec(values, "Blind"),
|
||||||
|
blinded_element: decode_vec(values, "BlindedElement"),
|
||||||
|
evaluation_element: decode_vec(values, "EvaluationElement"),
|
||||||
|
proof: decode(values, "Proof"),
|
||||||
|
proof_random_scalar: decode(values, "ProofRandomScalar"),
|
||||||
|
output: decode_vec(values, "Output"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(values: &JsonValue, key: &str) -> Vec<u8> {
|
||||||
|
values[key]
|
||||||
|
.as_str()
|
||||||
|
.and_then(|s| hex::decode(&s).ok())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> {
|
||||||
|
let s = values[key].as_str().unwrap();
|
||||||
|
let res = match s.contains(',') {
|
||||||
|
true => Some(s.split(',').map(|x| hex::decode(&x).unwrap()).collect()),
|
||||||
|
false => Some(vec![hex::decode(&s).unwrap()]),
|
||||||
|
};
|
||||||
|
res.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! json_to_test_vectors {
|
||||||
|
( $v:ident, $cs:expr, $mode:expr ) => {
|
||||||
|
$v[$cs][$mode]
|
||||||
|
.members()
|
||||||
|
.map(|x| populate_test_vectors(&x))
|
||||||
|
.collect::<Vec<VOPRFTestVectorParameters>>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vectors() -> Result<()> {
|
||||||
|
use p256::NistP256;
|
||||||
|
|
||||||
|
let rfc = json::parse(rfc_to_json(super::cfrg_vectors::VECTORS).as_str())
|
||||||
|
.expect("Could not parse json");
|
||||||
|
|
||||||
|
#[cfg(feature = "ristretto255")]
|
||||||
|
{
|
||||||
|
use crate::Ristretto255;
|
||||||
|
|
||||||
|
let ristretto_oprf_tvs = json_to_test_vectors!(
|
||||||
|
rfc,
|
||||||
|
String::from("ristretto255, SHA-512"),
|
||||||
|
String::from("OPRF")
|
||||||
|
);
|
||||||
|
assert_ne!(ristretto_oprf_tvs.len(), 0);
|
||||||
|
test_oprf_seed_to_key::<Ristretto255>(&ristretto_oprf_tvs)?;
|
||||||
|
test_oprf_blind::<Ristretto255>(&ristretto_oprf_tvs)?;
|
||||||
|
test_oprf_evaluate::<Ristretto255>(&ristretto_oprf_tvs)?;
|
||||||
|
test_oprf_finalize::<Ristretto255>(&ristretto_oprf_tvs)?;
|
||||||
|
|
||||||
|
let ristretto_voprf_tvs = json_to_test_vectors!(
|
||||||
|
rfc,
|
||||||
|
String::from("ristretto255, SHA-512"),
|
||||||
|
String::from("VOPRF")
|
||||||
|
);
|
||||||
|
assert_ne!(ristretto_voprf_tvs.len(), 0);
|
||||||
|
test_voprf_seed_to_key::<Ristretto255>(&ristretto_voprf_tvs)?;
|
||||||
|
test_voprf_blind::<Ristretto255>(&ristretto_voprf_tvs)?;
|
||||||
|
test_voprf_evaluate::<Ristretto255>(&ristretto_voprf_tvs)?;
|
||||||
|
test_voprf_finalize::<Ristretto255>(&ristretto_voprf_tvs)?;
|
||||||
|
|
||||||
|
let ristretto_poprf_tvs = json_to_test_vectors!(
|
||||||
|
rfc,
|
||||||
|
String::from("ristretto255, SHA-512"),
|
||||||
|
String::from("POPRF")
|
||||||
|
);
|
||||||
|
assert_ne!(ristretto_poprf_tvs.len(), 0);
|
||||||
|
test_poprf_seed_to_key::<Ristretto255>(&ristretto_poprf_tvs)?;
|
||||||
|
test_poprf_blind::<Ristretto255>(&ristretto_poprf_tvs)?;
|
||||||
|
test_poprf_evaluate::<Ristretto255>(&ristretto_poprf_tvs)?;
|
||||||
|
test_poprf_finalize::<Ristretto255>(&ristretto_poprf_tvs)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p256_oprf_tvs =
|
||||||
|
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("OPRF"));
|
||||||
|
assert_ne!(p256_oprf_tvs.len(), 0);
|
||||||
|
test_oprf_seed_to_key::<NistP256>(&p256_oprf_tvs)?;
|
||||||
|
test_oprf_blind::<NistP256>(&p256_oprf_tvs)?;
|
||||||
|
test_oprf_evaluate::<NistP256>(&p256_oprf_tvs)?;
|
||||||
|
test_oprf_finalize::<NistP256>(&p256_oprf_tvs)?;
|
||||||
|
|
||||||
|
let p256_voprf_tvs =
|
||||||
|
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("VOPRF"));
|
||||||
|
assert_ne!(p256_voprf_tvs.len(), 0);
|
||||||
|
test_voprf_seed_to_key::<NistP256>(&p256_voprf_tvs)?;
|
||||||
|
test_voprf_blind::<NistP256>(&p256_voprf_tvs)?;
|
||||||
|
test_voprf_evaluate::<NistP256>(&p256_voprf_tvs)?;
|
||||||
|
test_voprf_finalize::<NistP256>(&p256_voprf_tvs)?;
|
||||||
|
|
||||||
|
let p256_poprf_tvs =
|
||||||
|
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("POPRF"));
|
||||||
|
assert_ne!(p256_poprf_tvs.len(), 0);
|
||||||
|
test_poprf_seed_to_key::<NistP256>(&p256_poprf_tvs)?;
|
||||||
|
test_poprf_blind::<NistP256>(&p256_poprf_tvs)?;
|
||||||
|
test_poprf_evaluate::<NistP256>(&p256_poprf_tvs)?;
|
||||||
|
test_poprf_finalize::<NistP256>(&p256_poprf_tvs)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_oprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let server = OprfServer::<CS>::new_from_seed(¶meters.seed, ¶meters.key_info)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.sksm,
|
||||||
|
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_voprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let server = VoprfServer::<CS>::new_from_seed(¶meters.seed, ¶meters.key_info)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.sksm,
|
||||||
|
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.pksm,
|
||||||
|
CS::Group::serialize_elem(server.get_public_key()).as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_poprf_seed_to_key<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let server = PoprfServer::<CS>::new_from_seed(¶meters.seed, ¶meters.key_info)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.sksm,
|
||||||
|
&CS::Group::serialize_scalar(server.get_private_key()).to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.pksm,
|
||||||
|
CS::Group::serialize_elem(server.get_public_key()).as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests input -> blind, blinded_element
|
||||||
|
fn test_oprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?;
|
||||||
|
let client_result =
|
||||||
|
OprfClient::<CS>::deterministic_blind_unchecked(¶meters.input[i], blind)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.blind[i],
|
||||||
|
&CS::Group::serialize_scalar(client_result.state.blind).to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parameters.blinded_element[i].as_slice(),
|
||||||
|
client_result.message.serialize().as_slice(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests input -> blind, blinded_element
|
||||||
|
fn test_voprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?;
|
||||||
|
let client_blind_result =
|
||||||
|
VoprfClient::<CS>::deterministic_blind_unchecked(¶meters.input[i], blind)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.blind[i],
|
||||||
|
&CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parameters.blinded_element[i].as_slice(),
|
||||||
|
client_blind_result.message.serialize().as_slice(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests input -> blind, blinded_element
|
||||||
|
fn test_poprf_blind<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?;
|
||||||
|
let client_blind_result =
|
||||||
|
PoprfClient::<CS>::deterministic_blind_unchecked(¶meters.input[i], blind)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.blind[i],
|
||||||
|
&CS::Group::serialize_scalar(client_blind_result.state.get_blind()).to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parameters.blinded_element[i].as_slice(),
|
||||||
|
client_blind_result.message.serialize().as_slice(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests sksm, blinded_element -> evaluation_element
|
||||||
|
fn test_oprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let server = OprfServer::<CS>::new_with_key(¶meters.sksm)?;
|
||||||
|
let message = server.evaluate(&BlindedElement::deserialize(
|
||||||
|
¶meters.blinded_element[i],
|
||||||
|
)?);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
¶meters.evaluation_element[i],
|
||||||
|
&message.serialize().as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_voprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
|
||||||
|
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
|
||||||
|
let server = VoprfServer::<CS>::new_with_key(¶meters.sksm)?;
|
||||||
|
|
||||||
|
let mut blinded_elements = vec![];
|
||||||
|
for blinded_element_bytes in ¶meters.blinded_element {
|
||||||
|
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prepared_evaluation_elements = server.batch_evaluate_prepare(blinded_elements.iter());
|
||||||
|
let prepared_elements: Vec<_> = prepared_evaluation_elements.collect();
|
||||||
|
let VoprfServerBatchEvaluateFinishResult { messages, proof } =
|
||||||
|
server.batch_evaluate_finish(&mut rng, blinded_elements.iter(), &prepared_elements)?;
|
||||||
|
let messages: Vec<_> = messages.collect();
|
||||||
|
|
||||||
|
for (parameter, message) in parameters.evaluation_element.iter().zip(messages) {
|
||||||
|
assert_eq!(¶meter, &message.serialize().as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(¶meters.proof, &proof.serialize().as_slice());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_poprf_evaluate<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
<CS::Group as Group>::ScalarLen: Add<<CS::Group as Group>::ScalarLen>,
|
||||||
|
Sum<<CS::Group as Group>::ScalarLen, <CS::Group as Group>::ScalarLen>: ArrayLength<u8>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
|
||||||
|
let server = PoprfServer::<CS>::new_with_key(¶meters.sksm)?;
|
||||||
|
|
||||||
|
let mut blinded_elements = vec![];
|
||||||
|
for blinded_element_bytes in ¶meters.blinded_element {
|
||||||
|
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let PoprfServerBatchEvaluatePrepareResult {
|
||||||
|
prepared_evaluation_elements,
|
||||||
|
prepared_tweak,
|
||||||
|
} = server.batch_evaluate_prepare(blinded_elements.iter(), Some(¶meters.info))?;
|
||||||
|
let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
|
||||||
|
let PoprfServerBatchEvaluateFinishResult { messages, proof } =
|
||||||
|
PoprfServer::batch_evaluate_finish::<_, _, Vec<_>>(
|
||||||
|
&mut rng,
|
||||||
|
blinded_elements.iter(),
|
||||||
|
&prepared_evaluation_elements,
|
||||||
|
&prepared_tweak,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let messages: Vec<_> = messages.collect();
|
||||||
|
|
||||||
|
for (parameter, message) in parameters.evaluation_element.iter().zip(messages) {
|
||||||
|
assert_eq!(¶meter, &message.serialize().as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(¶meters.proof, &proof.serialize().as_slice());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests input, blind, evaluation_element -> output
|
||||||
|
fn test_oprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let client =
|
||||||
|
OprfClient::<CS>::from_blind(CS::Group::deserialize_scalar(¶meters.blind[i])?);
|
||||||
|
|
||||||
|
let client_finalize_result = client.finalize(
|
||||||
|
¶meters.input[i],
|
||||||
|
&EvaluationElement::deserialize(¶meters.evaluation_element[i])?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(¶meters.output[i], &client_finalize_result.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_voprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let mut clients = vec![];
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let client = VoprfClient::<CS>::from_blind_and_element(
|
||||||
|
CS::Group::deserialize_scalar(¶meters.blind[i])?,
|
||||||
|
CS::Group::deserialize_elem(¶meters.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(
|
||||||
|
¶meters.input,
|
||||||
|
&clients,
|
||||||
|
&messages,
|
||||||
|
&Proof::deserialize(¶meters.proof)?,
|
||||||
|
CS::Group::deserialize_elem(¶meters.pksm)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parameters.output,
|
||||||
|
batch_result
|
||||||
|
.map(|arr| arr.map(|message| message.to_vec()))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_poprf_finalize<CS: CipherSuite>(tvs: &[VOPRFTestVectorParameters]) -> Result<()>
|
||||||
|
where
|
||||||
|
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||||
|
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||||
|
{
|
||||||
|
for parameters in tvs {
|
||||||
|
let mut clients = vec![];
|
||||||
|
for i in 0..parameters.input.len() {
|
||||||
|
let blind = CS::Group::deserialize_scalar(¶meters.blind[i])?;
|
||||||
|
let client_blind_result =
|
||||||
|
PoprfClient::<CS>::deterministic_blind_unchecked(¶meters.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(¶meters.proof)?,
|
||||||
|
CS::Group::deserialize_elem(¶meters.pksm)?,
|
||||||
|
Some(¶meters.info),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let result: Vec<Vec<u8>> = batch_result.map(|arr| arr.unwrap().to_vec()).collect();
|
||||||
|
|
||||||
|
assert_eq!(parameters.output, result);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ciphersuite::CipherSuite,
|
|
||||||
errors::InternalError,
|
|
||||||
group::Group,
|
|
||||||
tests::{mock_rng::CycleRng, parser::*},
|
|
||||||
voprf::{
|
|
||||||
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
|
|
||||||
NonVerifiableServer, Proof, VerifiableClient, VerifiableServer,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use alloc::string::ToString;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use generic_array::GenericArray;
|
|
||||||
use json::JsonValue;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct VOPRFTestVectorParameters {
|
|
||||||
seed: Vec<u8>,
|
|
||||||
sksm: Vec<u8>,
|
|
||||||
pksm: Vec<u8>,
|
|
||||||
input: Vec<Vec<u8>>,
|
|
||||||
info: Vec<u8>,
|
|
||||||
blind: Vec<Vec<u8>>,
|
|
||||||
blinded_element: Vec<Vec<u8>>,
|
|
||||||
evaluation_element: Vec<Vec<u8>>,
|
|
||||||
proof: Vec<u8>,
|
|
||||||
proof_random_scalar: Vec<u8>,
|
|
||||||
output: Vec<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
|
|
||||||
VOPRFTestVectorParameters {
|
|
||||||
seed: decode(values, "seed"),
|
|
||||||
sksm: decode(values, "skSm"),
|
|
||||||
pksm: decode(values, "pkSm"),
|
|
||||||
input: decode_vec(&values, "Input"),
|
|
||||||
info: decode(values, "Info"),
|
|
||||||
blind: decode_vec(&values, "Blind"),
|
|
||||||
blinded_element: decode_vec(&values, "BlindedElement"),
|
|
||||||
evaluation_element: decode_vec(&values, "EvaluationElement"),
|
|
||||||
proof: decode(values, "Proof"),
|
|
||||||
proof_random_scalar: decode(values, "ProofRandomScalar"),
|
|
||||||
output: decode_vec(&values, "Output"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode(values: &JsonValue, key: &str) -> Vec<u8> {
|
|
||||||
values[key]
|
|
||||||
.as_str()
|
|
||||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
|
||||||
.unwrap_or(vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> {
|
|
||||||
let s = values[key].as_str().unwrap();
|
|
||||||
let res = match s.contains(',') {
|
|
||||||
true => Some(
|
|
||||||
s.split(',')
|
|
||||||
.map(|x| hex::decode(&x.to_string()).unwrap())
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
false => Some(vec![hex::decode(&s.to_string()).unwrap()]),
|
|
||||||
};
|
|
||||||
res.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! json_to_test_vectors {
|
|
||||||
( $v:ident, $cs:expr, $mode:expr ) => {
|
|
||||||
$v[$cs][$mode]
|
|
||||||
.members()
|
|
||||||
.map(|x| populate_test_vectors(&x))
|
|
||||||
.collect::<Vec<VOPRFTestVectorParameters>>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vectors() -> Result<(), InternalError> {
|
|
||||||
let rfc = json::parse(rfc_to_json(super::voprf_vectors::VECTORS).as_str())
|
|
||||||
.expect("Could not parse json");
|
|
||||||
|
|
||||||
use crate::tests::Ristretto255Sha512;
|
|
||||||
|
|
||||||
let ristretto_base_tvs = json_to_test_vectors!(
|
|
||||||
rfc,
|
|
||||||
String::from("ristretto255, SHA-512"),
|
|
||||||
String::from("Base")
|
|
||||||
);
|
|
||||||
|
|
||||||
let ristretto_verifiable_tvs = json_to_test_vectors!(
|
|
||||||
rfc,
|
|
||||||
String::from("ristretto255, SHA-512"),
|
|
||||||
String::from("Verifiable")
|
|
||||||
);
|
|
||||||
|
|
||||||
test_base_seed_to_key::<Ristretto255Sha512>(&ristretto_base_tvs)?;
|
|
||||||
test_base_blind::<Ristretto255Sha512>(&ristretto_base_tvs)?;
|
|
||||||
test_base_evaluate::<Ristretto255Sha512>(&ristretto_base_tvs)?;
|
|
||||||
test_base_finalize::<Ristretto255Sha512>(&ristretto_base_tvs)?;
|
|
||||||
|
|
||||||
test_verifiable_seed_to_key::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
|
|
||||||
test_verifiable_blind::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
|
|
||||||
test_verifiable_evaluate::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
|
|
||||||
test_verifiable_finalize::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "p256")]
|
|
||||||
{
|
|
||||||
use crate::tests::P256Sha256;
|
|
||||||
|
|
||||||
let p256_base_tvs =
|
|
||||||
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("Base"));
|
|
||||||
|
|
||||||
let p256_verifiable_tvs = json_to_test_vectors!(
|
|
||||||
rfc,
|
|
||||||
String::from("P-256, SHA-256"),
|
|
||||||
String::from("Verifiable")
|
|
||||||
);
|
|
||||||
|
|
||||||
test_base_seed_to_key::<P256Sha256>(&p256_base_tvs)?;
|
|
||||||
test_base_blind::<P256Sha256>(&p256_base_tvs)?;
|
|
||||||
test_base_evaluate::<P256Sha256>(&p256_base_tvs)?;
|
|
||||||
test_base_finalize::<P256Sha256>(&p256_base_tvs)?;
|
|
||||||
|
|
||||||
test_verifiable_seed_to_key::<P256Sha256>(&p256_verifiable_tvs)?;
|
|
||||||
test_verifiable_blind::<P256Sha256>(&p256_verifiable_tvs)?;
|
|
||||||
test_verifiable_evaluate::<P256Sha256>(&p256_verifiable_tvs)?;
|
|
||||||
test_verifiable_finalize::<P256Sha256>(&p256_verifiable_tvs)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_base_seed_to_key<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
let server = NonVerifiableServer::<CS>::new_from_seed(¶meters.seed)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.sksm,
|
|
||||||
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_verifiable_seed_to_key<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
let server = VerifiableServer::<CS>::new_from_seed(¶meters.seed)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.sksm,
|
|
||||||
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
|
|
||||||
);
|
|
||||||
assert_eq!(¶meters.pksm, &server.get_public_key().to_arr().to_vec());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests input -> blind, blinded_element
|
|
||||||
fn test_base_blind<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
for i in 0..parameters.input.len() {
|
|
||||||
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
|
|
||||||
let client_result = NonVerifiableClient::<CS>::blind(¶meters.input[i], &mut rng)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.blind[i],
|
|
||||||
&CS::Group::scalar_as_bytes(client_result.state.get_blind()).to_vec()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.blinded_element[i],
|
|
||||||
&client_result.message.serialize()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests input -> blind, blinded_element
|
|
||||||
fn test_verifiable_blind<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
for i in 0..parameters.input.len() {
|
|
||||||
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
|
|
||||||
let client_blind_result =
|
|
||||||
VerifiableClient::<CS>::blind(¶meters.input[i], &mut rng)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.blind[i],
|
|
||||||
&CS::Group::scalar_as_bytes(client_blind_result.state.get_blind()).to_vec()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.blinded_element[i],
|
|
||||||
&client_blind_result.message.serialize()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests sksm, blinded_element -> evaluation_element
|
|
||||||
fn test_base_evaluate<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
for i in 0..parameters.input.len() {
|
|
||||||
let server = NonVerifiableServer::<CS>::new_with_key(¶meters.sksm)?;
|
|
||||||
let server_result = server.evaluate(
|
|
||||||
BlindedElement::deserialize(¶meters.blinded_element[i])?,
|
|
||||||
&Metadata(parameters.info.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.evaluation_element[i],
|
|
||||||
&server_result.message.serialize()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_verifiable_evaluate<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
|
|
||||||
let server = VerifiableServer::<CS>::new_with_key(¶meters.sksm)?;
|
|
||||||
|
|
||||||
let mut blinded_elements = vec![];
|
|
||||||
for blinded_element_bytes in ¶meters.blinded_element {
|
|
||||||
blinded_elements.push(BlindedElement::deserialize(&blinded_element_bytes)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let batch_evaluate_result = server.batch_evaluate(
|
|
||||||
&mut rng,
|
|
||||||
&blinded_elements,
|
|
||||||
&Metadata(parameters.info.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for i in 0..parameters.evaluation_element.len() {
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.evaluation_element[i],
|
|
||||||
&batch_evaluate_result.messages[i].serialize(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(¶meters.proof, &batch_evaluate_result.proof.serialize());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests input, blind, evaluation_element -> output
|
|
||||||
fn test_base_finalize<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
for i in 0..parameters.input.len() {
|
|
||||||
let client = NonVerifiableClient::<CS>::from_data_and_blind(
|
|
||||||
¶meters.input[i],
|
|
||||||
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
|
||||||
¶meters.blind[i],
|
|
||||||
))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let client_finalize_result = client.finalize(
|
|
||||||
EvaluationElement::deserialize(¶meters.evaluation_element[i])?,
|
|
||||||
&Metadata(parameters.info.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
¶meters.output[i],
|
|
||||||
&client_finalize_result.output.to_vec()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_verifiable_finalize<CS: CipherSuite>(
|
|
||||||
tvs: &[VOPRFTestVectorParameters],
|
|
||||||
) -> Result<(), InternalError> {
|
|
||||||
for parameters in tvs {
|
|
||||||
let mut clients = vec![];
|
|
||||||
for i in 0..parameters.input.len() {
|
|
||||||
let client = VerifiableClient::<CS>::from_data_and_blind(
|
|
||||||
¶meters.input[i],
|
|
||||||
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
|
||||||
¶meters.blind[i],
|
|
||||||
))?,
|
|
||||||
&<CS::Group as Group>::from_element_slice(&GenericArray::clone_from_slice(
|
|
||||||
¶meters.blinded_element[i],
|
|
||||||
))?,
|
|
||||||
);
|
|
||||||
clients.push(client.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let batch_finalize_input = BatchFinalizeInput::new(
|
|
||||||
clients,
|
|
||||||
parameters
|
|
||||||
.evaluation_element
|
|
||||||
.iter()
|
|
||||||
.map(|x| EvaluationElement::deserialize(x).unwrap())
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let batch_result = VerifiableClient::batch_finalize(
|
|
||||||
batch_finalize_input,
|
|
||||||
Proof::deserialize(¶meters.proof)?,
|
|
||||||
CS::Group::from_element_slice(GenericArray::from_slice(¶meters.pksm))?,
|
|
||||||
&Metadata(parameters.info.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parameters.output,
|
|
||||||
batch_result
|
|
||||||
.outputs
|
|
||||||
.iter()
|
|
||||||
.map(|arr| arr.to_vec())
|
|
||||||
.collect::<Vec<Vec<u8>>>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,642 +0,0 @@
|
|||||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
//
|
|
||||||
// This source code is licensed under both the MIT license found in the
|
|
||||||
// LICENSE-MIT file in the root directory of this source tree and the Apache
|
|
||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
|
||||||
// of this source tree.
|
|
||||||
|
|
||||||
//! The VOPRF test vectors taken from:
|
|
||||||
//! https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
|
|
||||||
|
|
||||||
pub(crate) static VECTORS: &str = r#"
|
|
||||||
## OPRF(ristretto255, SHA-512)
|
|
||||||
|
|
||||||
### Base Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = caeff69352df4905a9121a4997704ca8cee1524a110819eb87deba1a39ec1
|
|
||||||
701
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf
|
|
||||||
8e03
|
|
||||||
BlindedElement = fc20e03aff3a9de9b37e8d35886ade11ec7d85c2a1fb5bb0b16
|
|
||||||
86c64e07ac467
|
|
||||||
EvaluationElement = 922e4c04b9f3b3e795d322a306c0ab9d96b667df9b949c05
|
|
||||||
2c8c75435a9dbf2f
|
|
||||||
Output = 9e857d0e8523b8eb9e995d455ae6ae19f75d85ac8b5df62c50616fb5aa0
|
|
||||||
ced3da5646698089c36dead28f9ad8e489fc0ee1c8e168725c38ed50f3783a5c520c
|
|
||||||
e
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037
|
|
||||||
e50b
|
|
||||||
BlindedElement = 483d4f39de5ff77fa0f9a0ad2334dd5bf87f2cda868539d21de
|
|
||||||
67ce49e7d1536
|
|
||||||
EvaluationElement = 6eef6ee53c6fb17c77ae47e78bdca2e1094f98785e7b9a14
|
|
||||||
f09be20797dad656
|
|
||||||
Output = b090b2ff80028771c14fecf2f37c1b14e46deec59c83d3b943c51d315bd
|
|
||||||
3bf7d32c399ed0c4ce6003339ab9ed4ad168bfb595e43530c9d73ff02ab0f1263d93
|
|
||||||
b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
### Verifiable Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = ac37d5850510299406ea8eb8fa226a7bfc2467a4b070d6c7bf667948b9600
|
|
||||||
b00
|
|
||||||
pkSm = 0c0254e22063cae3e1bae02fb6fa20882664a117c0278eda6bda3372c0dd9
|
|
||||||
860
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e326
|
|
||||||
3503
|
|
||||||
BlindedElement = 3a0a53f2c57e5ee0d89e394087f8e5f95b24159db01c31933a0
|
|
||||||
7f0e6414c954d
|
|
||||||
EvaluationElement = f8a50ed35a477b0cde91d926e1bc5ae59b97d5bd0dda51a7
|
|
||||||
28b0f036ec557d79
|
|
||||||
Proof = 7a5375eb1dbad259431f5c294e816a1c1483c279748da1a75d91f8a81438
|
|
||||||
ea08355d4087d4d848b46878dcc8fb5849ac7a09133382c2c6129564a7f7b4b7bf01
|
|
||||||
ProofRandomScalar = 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9d
|
|
||||||
bcec831b8c681a09
|
|
||||||
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
|
|
||||||
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
|
|
||||||
a
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd4171
|
|
||||||
ea02
|
|
||||||
BlindedElement = a86dd4544d0f3ea973926054230767dff16016215f2d73f26d3
|
|
||||||
f86a81f38cf1a
|
|
||||||
EvaluationElement = 9e47810f1de1b57ebe163a95c170ec165a2063f872155c37
|
|
||||||
6d94e8de2157af70
|
|
||||||
Proof = 61075125d851d5164b0aa1a4d5ddeebaf097266450ac6019579af5f7abd1
|
|
||||||
90088eb0f6f1e7f9d8bfddbc21ae3c25a065e6c4e797d15f345ed4fb9ee468d24c0a
|
|
||||||
ProofRandomScalar = 74ae06fd50d5f26c2519bd7b184f45dd3ef2cb50197d42df
|
|
||||||
9d013f7d6c312a0b
|
|
||||||
Output = fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f2
|
|
||||||
18047d1fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0
|
|
||||||
b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 3, Batch Size 2
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 80513e77795feeec6d2c450589b0e1b178febd5c193a9fcba0d27f0a06e0
|
|
||||||
d50f,533c2e6d91c934f919ac218973be55ba0d7b234160a0d4cf3bddafbda99e2e0
|
|
||||||
c
|
|
||||||
BlindedElement = c24645d6378a4a86ec4682a8d86f368b1e7db870fd709a45102
|
|
||||||
492bcdc17e904,0e5ec78f839a8b6e86999bc180602690a4daae57bf5d7f827f3d40
|
|
||||||
2f56cc6c51
|
|
||||||
EvaluationElement = 3afe48eab00493eb1b073e95f57a456cde9aefe463dd1e6d
|
|
||||||
0144bf6e99ce411c,daaf9421318fd2c7fcdf369cb348748cf4dd177cce30ee4d13c
|
|
||||||
eb1644b85b653
|
|
||||||
Proof = 601381ecbe127ada04c057b8b1fc21d912f71e49252780dd0d0ac768b233
|
|
||||||
ce035f9b489a994c1d14b92d603ebcffee4f5cfadc953f69bb62648c6e662613ae00
|
|
||||||
ProofRandomScalar = 3af5aec325791592eee4a8860522f8444c8e71ac33af5186
|
|
||||||
a9706137886dce08
|
|
||||||
Output = 4b2ff4c984985829c3cd9d90c255cdc0d6b61c4c0aafa9215769d51cf7d
|
|
||||||
eb01472ba945928a8305e010f12b7dcc75a9dc2460439e6297d57dc2ce7ca0abaae1
|
|
||||||
a,fe1fb7fa49c37dc7cd31d64859b4a2e6ae0cef294f2764e6f12f7d809f218047d1
|
|
||||||
fde147cf69807b8971fb2c316eb572be2b5bf491813bfec0a20668d6d07b0b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## OPRF(decaf448, SHAKE-256)
|
|
||||||
|
|
||||||
### Base Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
|
|
||||||
skSm = 78f2622804104209f7e015370ff98f4a3cbf311e6784e9f4944f8a252dc08
|
|
||||||
e916d9ab1a60dc905f0e56631903ecd4ae6e15291776d61460b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = d1080372f0fcf8c5eace50914e7127f576725f215cc7c111673c635ce668
|
|
||||||
bbbb9b50601ad89b358ab8c23ed0b6c9d040365ec9d060868714
|
|
||||||
BlindedElement = 984e0a443ee194090737df4afb402253f216b77650c91d252b6
|
|
||||||
638e1179723d51a4154b88eae396f1320f5df3c4b17f779516c456e364bd1
|
|
||||||
EvaluationElement = de477252a5ff3c7d51ce159cb8ccf1865d8c7d3402824163
|
|
||||||
8d80971f13a59d87b2b1036341b98089555ab088278391794c49bbb052fdbcff
|
|
||||||
Output = df8f910c3b84d1f3ca6afd1992768608a20f2ad7b770e9d89d303c88ba1
|
|
||||||
5bb7d991f2f7ffd5b5b51fa3bcf8fa06779609497f6c0ae4e9cb2dcd48c68b4ac6b9
|
|
||||||
4
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = aed1ffa44fd8f0ed16373606a3cf7df589cca86d8ea1abbf5768771dbef3
|
|
||||||
d401c74ae55ba1e28b9565e1e4018eb261a14134a4ce60c1c718
|
|
||||||
BlindedElement = 4aa751f84b2634b73efa364b03e60b92b84f457576e6b369eea
|
|
||||||
b76140e3859d10d2e98174f13f5a2c70670529ccf093d5f1aaf355b4f830b
|
|
||||||
EvaluationElement = 085ea1cb452a2fb15b3a0d0e1c86899c7ea49fe2e4856ef4
|
|
||||||
f95bc2542eec610fc09b0fe7d7ed7389d86af6a646695b7ad46527dc2a936aa4
|
|
||||||
Output = b57516a737879ece1110ad5d051ac0a6c54e1dcd989c907721ecebab5b4
|
|
||||||
5877cc693c3c05d0bd416c5a9ceba36de41a0a31679c146fe4c110c64b056eba1720
|
|
||||||
b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
### Verifiable Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
|
|
||||||
skSm = 9eb722f7fee9f61f24ad31bc42309f73648cf4393929e8f5f333fe10c6975
|
|
||||||
c827a1eba4e03ae2fa8735db2f63f6c98c7af6010e64c81f535
|
|
||||||
pkSm = b6e2751176d57836fe1dfbdbbdc78a1b5c5a52f831226c9d8dfdf5daf8f46
|
|
||||||
6e310e80978e9b81c387f5bc85cc7ef5567f4dd3ba7674579a2
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 4c936db1779a621b6c71475ac3111fd5703a59b713929f36dfd1e892a7fe
|
|
||||||
814479c93d8b4b6e11d1f6fe5351e51457b665fa7b76074e531f
|
|
||||||
BlindedElement = d0b8e2eecad2816d45c1f8a072fe6db77d18f4b26f0889c98e2
|
|
||||||
ef856ac5df82090c1fbeac9c8e732f192b66c3b4c3f1e446ab8910c86be2f
|
|
||||||
EvaluationElement = d29849d8ad1e651328e8119003debd9ecd54cc786a5eb8ae
|
|
||||||
ea56487ffc09120e98792f9475605488d16623b8e3cfa5af1ec27e76bc841b75
|
|
||||||
Proof = 8b3b8f0c9eb22527e419f5a03d4d3f34cf725837424a38c5b4f88c7759f7
|
|
||||||
a54bade57b7930bfeff051be9bfeaabc8976ed407398e0ce462a062e068a8d57bc1c
|
|
||||||
411bc42fe714626cfb92ad854a56636c2b83f2b5215c2ff531b22e4d37031523db20
|
|
||||||
3556959e275b46b84303ed23fc37
|
|
||||||
ProofRandomScalar = 1b3f5a55b2f18f8c53d4ecf2e1c27e1028f1c345bb504486
|
|
||||||
4aa9dd8439d7520a7ba6183d50ef08bdf6c781aa465660c93e8195a8d231b62f
|
|
||||||
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
|
|
||||||
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
|
|
||||||
d
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 15b3355179392f40c3d5a15f0d5ffc354e340454ec779f575e4573a3886a
|
|
||||||
b5e57e4da2985cea9e32f6d95539ce2c7189e1bd7462e8c5483a
|
|
||||||
BlindedElement = 0e7ddd85c8bc5382e908241c6151afe23a41e0396759b5e38a9
|
|
||||||
affd996cd822bca242a499793555fc15f07bffdaaa93b42568b307fbdca0f
|
|
||||||
EvaluationElement = 4c81e29e8a9502fa02e00cb09cf40d9b98988ac9b4bce7cc
|
|
||||||
a0656caeb0926b59c7000d7fe6c5dd814f831864547d2360d223a50077bd04fe
|
|
||||||
Proof = 74fc8fbf2e669dc5d25898ea8ce45d1d3eb97edb4b7c3cee39865a3c66da
|
|
||||||
6b7bad4ad3e77794d6f5e82fa8a645b9b973a8612bfcd1194302f700ee3433e876d8
|
|
||||||
3f96bb70f19ff292605ad4c9466fd71dbc2ed22ade0130574e5ee343ef45d42e834a
|
|
||||||
11a19fd6f5b1b5ef910bcccf731b
|
|
||||||
ProofRandomScalar = 2f2e9955be83a4b25743ebd3618d4fad8b7288477da50bed
|
|
||||||
9befa58af639ddd950fec34205f8a4f166fadcb8fa71a3ffdd2e98f4c8ef5e26
|
|
||||||
Output = 2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c
|
|
||||||
07f59055e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f
|
|
||||||
9
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 3, Batch Size 2
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 614bb578f29cc677ea9e7aea3e4839413997e020f9377b63c13584156a09
|
|
||||||
a46dd2a425c41eac0e313a47e99d05df72c6e1d58e6592577a0d,4c115060bca87db
|
|
||||||
7d73e00cbb8559f84cb7a221b235b0950a0ab553f03f10e1386abe954011b7da62bb
|
|
||||||
6599418ef90b5d4ea98cc28aff517
|
|
||||||
BlindedElement = 5e481a4d7eaa5bab831f53f9a6311851dafd4318c6462eed4f6
|
|
||||||
15004afdb082da2f99670b0963985faac21c30eea19aacfc441412edb4c0b,8e043b
|
|
||||||
9b7afeafa07e39d9b8b88957ff07d69124b1a2b841e18c9ffb52ebf0c25144eb2501
|
|
||||||
a1d7983a44604f33a36e925eebc9bec65d9c54
|
|
||||||
EvaluationElement = 8a0d34fdb0b55121421546ff952c7bd3cbe469926ff9ad4f
|
|
||||||
aeba243823955529eeae4f1a7a64cd055ec01baa041a99dfbe1a67ca4d59f93d,5e8
|
|
||||||
6e0b41cc88186ee0003baa46535e71acd98453b298976b92be2cca2646e88620f55d
|
|
||||||
f6bf4754456dfd8d84f6889c17b5ff93052325a1a
|
|
||||||
Proof = 1ff624a102b99771c76a9414e9b3f33127897d971bc84a922e464805e4a9
|
|
||||||
f27b889922030adebbbd58e0ab618ade9c84bfe8aa226176f11f432958ea1e6f6926
|
|
||||||
3aef51db9efb23ee504d233c17e9077c0373401da167637a1df4eafd9c2537c9f89c
|
|
||||||
103f9e635931fe2042419dd9bd37
|
|
||||||
ProofRandomScalar = a614f1894bcf6a1c7cef33909b794fe6e69a642b20f4c911
|
|
||||||
8febffaf6b6a31471fe7794aa77ced123f07e56cc27de60b0ab106c0b8eab127
|
|
||||||
Output = 1ff5c5c2c081c76006b52c45f79728882dc48962036ea7d4d5097b04e93
|
|
||||||
9ae81118a7fe5f0a66a6131bef18b9cd998150f10c62619ec4c2d223ea57dc67f153
|
|
||||||
d,2753e222528f1ee5fcc6ad4bf1ca953e5d3b47c1dfae85710f46a0a030c07f5905
|
|
||||||
5e9b05dacb729a7ce41cd2ed782f8a76a1b3f74b40196aed0b6938b89c60f9
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## OPRF(P-256, SHA-256)
|
|
||||||
|
|
||||||
### Base Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = a1b2355828f2c76de6749af9d093bd9fe0f2cada3ec653cd9a6d3126a7a78
|
|
||||||
27b
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 5d9e7f6efd3093c32ecceabd57fb03cf760c926d2a7bfa265babf29ec98a
|
|
||||||
f0d0
|
|
||||||
BlindedElement = 03e3c379698da853d9844098fa0ac676970d5ec24167b598714
|
|
||||||
cd2ee188604ddd2
|
|
||||||
EvaluationElement = 030d8d882120e8fa67ef978a9abac506acd5ec731b8e8d6f
|
|
||||||
15035e29241dd2ced2
|
|
||||||
Output = ab653a4f3b357177b125e1c6d0bd2c0bc409b7ed5f48c99537fbd7fd11e
|
|
||||||
f8133
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 825155ab61f17605af2ae2e935c78d857c9407bcd45128d57d338f1671b5
|
|
||||||
fcbe
|
|
||||||
BlindedElement = 030b40be181ffbb3c3ae4a4911287c43261f5e4034781def69c
|
|
||||||
51608f372a02102
|
|
||||||
EvaluationElement = 03991df04e3e526d457065b6eafc855aa2fc4528c22d2b51
|
|
||||||
6a3c71227b1b488f44
|
|
||||||
Output = eca4df985f7c49b091c3ce4217be1f26cdc6a148b681ed1f1638d09dfd2
|
|
||||||
13e6e
|
|
||||||
~~~
|
|
||||||
|
|
||||||
### Verifiable Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = 4e7804245a743c59d624457677294e04a8bc4bdcd94f0d3bd54f568067489
|
|
||||||
d34
|
|
||||||
pkSm = 03b51a0af95c819b09ee80c2056cf0ab0551a5355266d3a0aaff90c3fe915
|
|
||||||
ed892
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = cee64d86fd20ab4caa264a26c0e3d42fb773b3173ba76f9588c9b14779bd
|
|
||||||
8d91
|
|
||||||
BlindedElement = 0222f5dba2da1ec7bd1086d0e04894ef1da1c11163daf376b2b
|
|
||||||
c76cc51edb16815
|
|
||||||
EvaluationElement = 02f2767135f75f69b257675b38f2bcd50338a655a5092166
|
|
||||||
3c8942ca61ea7d3c29
|
|
||||||
Proof = ffa082fc9f9a287e7edc50e3ad879ee13aebd24b69124792bdf047c643f7
|
|
||||||
0af2b50907b2fa188b90aff3b25e1d9abb02e9e2c8bfdc525c61ca008428940fca64
|
|
||||||
ProofRandomScalar = 70a5204b2b606f5a28328916e1e5ea5a17862d7a261fdd6d
|
|
||||||
959759758d5e34ac
|
|
||||||
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
|
|
||||||
94448
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 5c4b401063eff0bf242b4cd534a79bacfc2e715b2db1e7a3ad4ff8af1b24
|
|
||||||
daa2
|
|
||||||
BlindedElement = 02f84403d1ceb40a3668349f7c349f806d2c858785853324c66
|
|
||||||
7505018d13ee160
|
|
||||||
EvaluationElement = 0216d7d342ef50113244b444dfedaec78810959e40fef0a6
|
|
||||||
922658d44accb1e9c1
|
|
||||||
Proof = f496e58818c25ffb386f22ceb57a83da1200612b67aaa07608b3375c25b2
|
|
||||||
97e03e67d1f6094a8012725dc63a0c2f4f870173b97a3daa03588f777655a087fbbf
|
|
||||||
ProofRandomScalar = 3b9217801b5d51cef66d9fdbd94a53533e7c5057e09e2200
|
|
||||||
65ea8c257c0dd606
|
|
||||||
Output = 90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b9
|
|
||||||
2fe28
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 3, Batch Size 2
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = f0c7822ba317fb5e86028c44b92bd3aedcf6744d388ca013ef33edd36930
|
|
||||||
4eda,3b9631be9f8b274d9aaf671bfb6a775229bf435021b89c683259773bc686956
|
|
||||||
b
|
|
||||||
BlindedElement = 02a840214a74345570dcadfc927e726901b257b447234fac509
|
|
||||||
0a1830295ca736c,039a5a8152abb0154b4d79a90486e358ea325980f0bf590524c4
|
|
||||||
460f700454238f
|
|
||||||
EvaluationElement = 025991aac0b0c79bb1185c0b1e64964656634dfcd755cdf5
|
|
||||||
da9ee52be0b5d5f742,03319e3baba8fa7f60dab49ef0ba68b7a85bccb5d4968643e
|
|
||||||
2f029b6c0826911d1
|
|
||||||
Proof = 51b5ed453168480a2e95863cda1f4d28ad5bc91e8c9c75d788569aea1679
|
|
||||||
794a642087db120a2b3ce839f57041801f37cd4a6c05b69b327b877810293f7b09a8
|
|
||||||
ProofRandomScalar = 8306b863276ae74049615162a416d507a6532c99c1ea3f03
|
|
||||||
d05f6e78dc1edabe
|
|
||||||
Output = c74d46cc93e578f7048bc6b852cd9bc1d9ebb90c586308f9202b9deedc8
|
|
||||||
94448,90a9f5ff4208a5505d1b7ed65eb233bb61b4c999ffa0d8cd1d98fb717b92fe
|
|
||||||
28
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## OPRF(P-384, SHA-512)
|
|
||||||
|
|
||||||
### Base Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
|
|
||||||
skSm = ef1b52c12cdf43dc260bf5425a30cde7d708ec34b38dcfbdc2946d7baf525
|
|
||||||
361e797f6a98f1ebd80f64865f21cde1c6d
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 359073c015b92d15450f7fb395bf52c6ea98384c491fe4e4d423b59de7b0
|
|
||||||
df382902c13bdc9993d3717bda68fc080b99
|
|
||||||
BlindedElement = 02fa3115c21ffcacc09ca470729b725781f84333e217cfeec2b
|
|
||||||
8ba6a54ce492ede7ead3714c5b177427ef853effb1b5c24
|
|
||||||
EvaluationElement = 033a4bdea2693686e4ce467c8a5cdfc41b86ad20aaaa9bc1
|
|
||||||
6e75b59dbd41dab0bc9af0041e551ece3b4c9fb2315d8d1fa9
|
|
||||||
Output = a5a0ef3fb964a36097662d1258ef0f93b224ddd81a356c37d5dd05a885a
|
|
||||||
0b6722b90c1f5181637fece7ed180ba053da23bf35cef7a87dcba75562cb7a264001
|
|
||||||
8
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 21ece4f9b6ffd01ce82082545413bd9bb5e8f3c63b86ae88d9ce0530b01c
|
|
||||||
b1c23382c7ec9bdd6e75898e4877d8e2bc17
|
|
||||||
BlindedElement = 025fddc89a832089a59120df742acb34dba82b26afcae977961
|
|
||||||
57df238b5905c494a23c56b1f485cbbff78d31df7fa1492
|
|
||||||
EvaluationElement = 02f8b59813663e7965c219c113c560482cbea7ca4c412a0c
|
|
||||||
f3fd855ee7d543ae926d29ace85296f195f988be284b2347f6
|
|
||||||
Output = f2a0b355cae4ae2c717d0b48e39c0ee356db3ca446fddf85cddb74f397e
|
|
||||||
b85046da62d0d85d55d19d39dd9b68fcc39379ec6d3b93ba33909fcc96361d225cdd
|
|
||||||
e
|
|
||||||
~~~
|
|
||||||
|
|
||||||
### Verifiable Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
|
|
||||||
skSm = 67ee1c9e67566d87bfcca9e5dac4bfdb8bdd727c031133fac2aa9ba6c41e6
|
|
||||||
1e5f8fd401b5d76c7d54b15b15932797479
|
|
||||||
pkSm = 029b51b2ce9c499f2056e65e0f41d60960f9c4795c0cf94af273ce840c20b
|
|
||||||
e4cdf87690b6b121b37d399b49afcc2ec9ac3
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 102f6338df84c9602bfa9e7d690b1f7a173d07e6d54a419db4a6308f8b09
|
|
||||||
589e4283efb9cd1ee4061c6bf884e60a8774
|
|
||||||
BlindedElement = 02a1f41323e91a6ac9fbbb5b8e4c7c58a4c5bcbaa4195557182
|
|
||||||
cd59e826dc847f1e077de1d402ac92eafe322461fc0d582
|
|
||||||
EvaluationElement = 03af3164f8721a57931f92884b43c58ff0ed1be249f7e1c9
|
|
||||||
3033a5909f0ffc59ed3fea9452ec5c9cfb865b8bd2e65cd209
|
|
||||||
Proof = 44108ca9b342f4d7e31a250aa9f41afb0de840e113dbb6bb82b5e6735aef
|
|
||||||
18a20867a63628be6e109d2d687e1faa8888270f1173bc6f916e21142096d23d1719
|
|
||||||
4edf844074922c287a50182f87bbb5fc3a966c8851dd6799ec5cfe59c7063c7f
|
|
||||||
ProofRandomScalar = 90f67cafc0ffaa7a1e1d1ced3c477fea691e696032c8709c
|
|
||||||
86cbcda2b184ad0029d29abeabede9788d11782429bff297
|
|
||||||
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
|
|
||||||
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
|
|
||||||
0
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 8aec1d0c3d16afd032da7ba961449a56cec6fb918e932b06d5778ac7f67b
|
|
||||||
ecfb3e3869237f74106241777f230582e84a
|
|
||||||
BlindedElement = 02b3465d70f76de3eaf6ecb8080490288f741c622c06d023bd1
|
|
||||||
80a55a2e3e4eaad08533651f9d278a3f59cec8277780303
|
|
||||||
EvaluationElement = 03a53e01901893585437cd48a1eea1188fc8e9275a80cf43
|
|
||||||
370a451c476dae3b84ca8c7bf44fcac2fa3eeab933b25da0c3
|
|
||||||
Proof = 5ebc467e78ae29f7d741221df0ee67285df72ec482fdc8e5bde7e588b12f
|
|
||||||
cba86f4f116c23ee6b32c0f38f2daac67e869e53e7e0494cc883e4984daf10a55819
|
|
||||||
bbb5ce7e9005f143b3dda88d8a35649269a4658a98c81c814097d15a3dcf4dbe
|
|
||||||
ProofRandomScalar = bb1876a7f7165ac7ec79bfd5213ea2e374252f29a6e19915
|
|
||||||
f81b0c7dcea93ce6580e089ede31c1b6b5b33494581b4868
|
|
||||||
Output = 5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920
|
|
||||||
267a40b246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92
|
|
||||||
c
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 3, Batch Size 2
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 41fabd4722d92472d858051ce9ad1a533176a862c697b2c392aff2aeb77e
|
|
||||||
b20c2ae6ba52fe31e13e03bf1d9f39878b23,51171628f1d28bb7402ca4aea6465e2
|
|
||||||
67b7f977a1fb71593281099ef2625644aee0b6c5f5e6e01a2b052b3bd4caf539b
|
|
||||||
BlindedElement = 02d715dfce1a0724071fa8e530d79f7b234a31739a64166e0fe
|
|
||||||
21fa6fa0fe19e1ab5e468becca899f31e365c47f3efb2ef,028dfd0c7a38b4cb8477
|
|
||||||
cae34f041344fb44fc9e55bfa3cf55ab7b4764b74accc7b49c0ff09a524598033dad
|
|
||||||
1152fb3a1c
|
|
||||||
EvaluationElement = 03f9a8c81c108201888eb86348c6f80691d99425272972b5
|
|
||||||
bf41d3038af0eeb04d60edd9ea288625a7166a8c17cea0083f,02abb31980533dbf7
|
|
||||||
eb5fee0a8969089b3e16585a2cd41a34067592a2021b1b4ea3d1cef3e7c87a6f284c
|
|
||||||
0e45546c92d98
|
|
||||||
Proof = f0f7bd2723c3460d5c5ab03092c6861fb34253470ef430dac9aeac6ce489
|
|
||||||
84b28d91178061cba02e3e911c4aa97229d519755db385ddd08064fdf8405897d1de
|
|
||||||
a472688934088505e89dcff91081fec1d2e37c1d4c5a9dddbdd358aa89f63b46
|
|
||||||
ProofRandomScalar = 1b538ff23749be19e92df82df1acd3f606cc9faa9dc7ab25
|
|
||||||
1997738a3a232f352c2059c25684e6ccea420f8d0c793fa0
|
|
||||||
Output = 065094c66d66b6541aa1e09d99e2fdaac727356e9cd1c18275b7127be51
|
|
||||||
eb1ce7f37ad5924f7425d60828c2d1acc69bef40d11423bba8f9e34478e04c437fbe
|
|
||||||
0,5f557169680da50500b5333a26bb2ba79256c0ecc351051d32cac540920267a40b
|
|
||||||
246deb286c9ecb0025dede808465f85d6a5e75aca61088533b306d8646c92c
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## OPRF(P-521, SHA-512)
|
|
||||||
|
|
||||||
### Base Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = 016ee706f30ce7e15e4ffa3114c7d59a7b6f302d531ca60419be39d1cd43e
|
|
||||||
e13b1fc8398b7f63a900cdc49c6e99f65a74403db2fa739927a2ee288cff857d9d84
|
|
||||||
ecf
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 01b983705fcc9a39607288b935b0797ac6b3c4b2e848823ac9ae16b3a3b5
|
|
||||||
816be03432370deb7c3c17d9fc7cb4e0ce646e04e42d638e0fa7a434ed340772a8b5
|
|
||||||
d626
|
|
||||||
BlindedElement = 0301f0a8c68e58f5571bd39fe3b0b2aa055a8c34e3d68ba0d2e
|
|
||||||
d177db0bc7575d477ed8f557596feb5ac568fe738eee8cff7dcb56dc78f52bf381c0
|
|
||||||
912e0e84b5a3f5b
|
|
||||||
EvaluationElement = 0200d7b1131aa9f8c365de7bd7903738f61bdecfaada375a
|
|
||||||
ba3905bdaad1301c7cd537f69abff04140ccca29a4c46cb4a036160e55a9621210b3
|
|
||||||
71d84646b0199571fa
|
|
||||||
Output = 61eea8fedfa9338dd22fac279f1f3f9e96693919c59ea3918c7a441115e
|
|
||||||
6bdecb1d05b5da55d4024858c92d3911a81d4eca362123b2911e5dc58591bf7be29c
|
|
||||||
7
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 01a03b1096b0316bc8567c89bd70267d35c8ddcb2be2cdc867089a2eb5cf
|
|
||||||
471b1e6eb4b043b9644c8539857abe3a2022e9c9fd6a1695bbabe8add48bcd149ff3
|
|
||||||
b841
|
|
||||||
BlindedElement = 030099c35342a43221c6e03debfb17bad71b62e04c9242aa6e9
|
|
||||||
f2f915163ef4f5b8b7fe1740a4d636c36bd5c73ca39c69992dc7f6dff8f232125efc
|
|
||||||
22af4df8352fea2
|
|
||||||
EvaluationElement = 0300ceeba6751486eecc479ab2259e3a57c13b0710f61c82
|
|
||||||
87acad60624974b76ea242dbcae3a9daad1bdc9c49012c8d8b384d510980cc1ef8fa
|
|
||||||
8d10502748ce63d93f
|
|
||||||
Output = 6682273a5199b2454a706cac557008e2264580ac39b6995e1f47130b985
|
|
||||||
d1015de7713d3bdb121212a68de2ece73bf72e41738a01c23428753c44e3dd39b5de
|
|
||||||
3
|
|
||||||
~~~
|
|
||||||
|
|
||||||
### Verifiable Mode
|
|
||||||
|
|
||||||
~~~
|
|
||||||
seed = a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
|
|
||||||
3a3
|
|
||||||
skSm = 0017674057e06c5e3e8a331f2dc3558540701c9cd0f4c19126d5972af6a01
|
|
||||||
447b312d05a06dab3e9e07c891d749444c27ede0897ad42aea03b887eb5db93e3f29
|
|
||||||
a86
|
|
||||||
pkSm = 0201ee4e2eaa74728f577f4bb282c5440cd454fdee1d79b15a36d34b5e5a1
|
|
||||||
25e3ccc0f99e32cc0a6a15b5652a0c8a424860c6753f685d0e1e150ceba24ca3386f
|
|
||||||
29216
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 1, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 00bbb82117c88bbd91b8954e16c0b9ceed3ce992b198be1ebfba9ba970db
|
|
||||||
d75beefbfc6d056b7f7ba1ef79f4facbf2d912c26ce2ecc5bb8d66419b379952e96b
|
|
||||||
d6f5
|
|
||||||
BlindedElement = 0200357f949a0a0bdfeb682734dbdeb778f3845045617b21436
|
|
||||||
27753332e2e75458ab183b12635c75e19afaf56981e7755803026842db1b22fa42c8
|
|
||||||
61413d07ff86545
|
|
||||||
EvaluationElement = 0201d636bac3f77c1091b337daae32259a3eacd57e3c0fb1
|
|
||||||
444fe5ce22af6acdcef4a46a2b5e169aa8d0e26ec2a3621c15dd366ba1978dae761c
|
|
||||||
1ef3dac63c60cbee88
|
|
||||||
Proof = 011ebe27ebc79e5679b643c6b3a51333499c7abee86c092181c0a8e7e539
|
|
||||||
e0ba30b1c128666708c753696ace2aa789c4975b0b80d6241a1dafe85c39a7338d1e
|
|
||||||
20d00131c8a81b5f64209f8fe53e8c6a00789a893f20596198e2521275e05d925298
|
|
||||||
08e9f54030fc8be2ce78c6df0d29e6fd7d8e623e0ccc7b19b194493dacd2a4eb3a32
|
|
||||||
ProofRandomScalar = 00ce4f0d824939827888f4c28773466f3c0a05741260040b
|
|
||||||
c9f302a4fea13f1d8f2f6b92a02a32d5eb06f81de7960470f06169bee12cf47965b7
|
|
||||||
2a59946ca3879670
|
|
||||||
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
|
|
||||||
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
|
|
||||||
5
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 2, Batch Size 1
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 009055c99bf9591cb0eab2a72d044c05ca2cc2ef9b609a38546f74b6d688
|
|
||||||
f70cf205f782fa11a0d61b2f5a8a2a1143368327f3077c68a1545e9aafbba6a90dc0
|
|
||||||
d40a
|
|
||||||
BlindedElement = 030185e431f056e75ba7fac49da70790031daa333d16f05e1de
|
|
||||||
471e24afe0ed985c770ce77bd1bebec527e9a76feecc6afd92c5fd00481ba7fb843d
|
|
||||||
2aab52337cb716e
|
|
||||||
EvaluationElement = 02000859e1abc2ed28086b854ec5ae72311244fdeedf81d7
|
|
||||||
69af6a6f2c83f00fa48df1f1a0c0b6fac84cc654b7757ac042107a6b3043e483bb3b
|
|
||||||
74de5d6c301b20e8f6
|
|
||||||
Proof = 01629dd5af14c7414801d879b1018ce06bcc5c5d0a64ca422b76aaa531c8
|
|
||||||
ecca630919fb4b51fa60fdc215f73e67e8d617d55ca6a227343d434d5e0f487567f8
|
|
||||||
5bfa016959443267bb7d9a5c5e5b1c4d20026394b4edaca7dfbc1aa3b3c2020cf995
|
|
||||||
79cf276c0e84f0cb5a820226fa3b81d42de2db39d8412642e70428e485a61ee9d760
|
|
||||||
ProofRandomScalar = 00b5dfc19eb96faba6382ec845097904db87240b9dd47b1e
|
|
||||||
487ec625f11a7ba2cc3de74c5078a81806f74dd65065273c5bd886c7f87ff8c5f39f
|
|
||||||
90320718eff747e3
|
|
||||||
Output = 7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d
|
|
||||||
1b2e936669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c13011057
|
|
||||||
1
|
|
||||||
~~~
|
|
||||||
|
|
||||||
#### Test Vector 3, Batch Size 2
|
|
||||||
|
|
||||||
~~~
|
|
||||||
Input = 00,5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
|
|
||||||
Info = 7465737420696e666f
|
|
||||||
Blind = 01c6cf092d80c7cf2cb55388d899515238094c800bdd9c65f71780ba85f5
|
|
||||||
ae9b4703e17e559ca3ccd1944f9a70536c175f11a827452672b60d4e9f89eba28104
|
|
||||||
6e29,00cba1ba1a337759061965a423d9d3d6e1e1006dc8984ad28a4c93ecfc36fc2
|
|
||||||
171046b3c4284855cfa2434ed98db9e68a597db2c14728fade716a6a82d600444b26
|
|
||||||
e
|
|
||||||
BlindedElement = 0301978860af75cd69acbc93e8c9fc530e5d2b2208da42c65bf
|
|
||||||
e079f0f6e0b3fc6080556c10739271d2a8fe578409d4fa9b19ef0484d9c15451c4e7
|
|
||||||
0501e31da7608cb,0200e30565c3d7e02c822762f25db4c872811adb2cbfbad92b04
|
|
||||||
291bc8c476d0546d1c5ecf5c58ff06b8d19aad8eca9e5f1a80ff8e981ebc490b0cfb
|
|
||||||
d5d499b47bad8e
|
|
||||||
EvaluationElement = 0300abdee910f144c3be460e724c11626e1f9986f72e2c43
|
|
||||||
3a9c4dad2ef6fcb9249c9a5036334ba88b0892462b6f8ad419c38cc259b0c774a9bd
|
|
||||||
0c4d545d0914413ea2,02019696f91dcc178bbe6b97f822cdc4052f9b94852ff6023
|
|
||||||
f6068848f867df40e54a5f1525e7fafa383e82fe36bf3c74427b51903032d0f89876
|
|
||||||
05bf24ee003f37693
|
|
||||||
Proof = 008fa896b69c1efc4e9c6bdfd0b149444532d5ba3bfd957cf7cd71c374d3
|
|
||||||
a1cca25f17b60616164377b0734243bc878e17d3ecab36b3e3565b5c6218dae92d40
|
|
||||||
c0be018707381f6f4b0153044737030b5d9851c15609532da8932c1fa1f4901dba05
|
|
||||||
a4118d25142344f9ea1465c907eb13d908a45d8b98265eac48819a04cae859b0643a
|
|
||||||
ProofRandomScalar = 00d47b0d4ca4c64825ba085de242042b84d9ebe3b2e9de07
|
|
||||||
678ff96713dfe16f40f2c662a56ed2db95e1e7bf2dea02bd1fa76e953a630772f68b
|
|
||||||
53baade9962d1646
|
|
||||||
Output = c51295e2a03ba59f1538734316e0d70dd81f95daba2f7b5ac4906c56ce8
|
|
||||||
79d6cef8f583433c981a182a52dd568811b073f65fc1124941f344cc9dd3b3880f29
|
|
||||||
5,7462f460340a52f7b7609c5e1c5e2d5334d43da7631cb549bb65163a05d1b2e936
|
|
||||||
669e52e66c92da4b2e24fff3c118c62787577c01d2885567b476c130110571
|
|
||||||
~~~
|
|
||||||
"#;
|
|
||||||
+604
-794
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
[formatting]
|
||||||
|
allowed_blank_lines = 1
|
||||||
|
reorder_keys = true
|
||||||
Reference in New Issue
Block a user