diff --git a/.cargo/license.rs b/.cargo/license.rs index 3fa8d9f..664968b 100644 --- a/.cargo/license.rs +++ b/.cargo/license.rs @@ -1,6 +1,7 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4345a70..b4eaf4b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,9 @@ on: push: branches: - main + - v0.4 pull_request: - types: [opened, repoened, synchronize] + types: [opened, reopened, synchronize] jobs: cargo-audit: @@ -12,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache cargo-audit - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/.crates.toml @@ -24,7 +25,7 @@ jobs: run: cargo install cargo-audit - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run cargo audit run: cargo audit -D warnings @@ -35,8 +36,7 @@ jobs: fail-fast: false matrix: backend_feature: - - --features ristretto255-ciphersuite,ristretto255-u64 - - --features ristretto255-ciphersuite,ristretto255-u32 + - --features ristretto255-ciphersuite - frontend_feature: - @@ -44,11 +44,11 @@ jobs: - --features serde toolchain: - stable - - 1.57.0 + - 1.65.0 name: test steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install ${{ matrix.toolchain }} toolchain uses: actions-rs/toolchain@v1 @@ -75,6 +75,12 @@ jobs: command: test args: --no-default-features ${{ matrix.frontend_feature }},std ${{ matrix.backend_feature }} + - name: Run cargo test with all features enabled + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features + build-no-std: name: Build with no-std on ${{ matrix.target }} runs-on: ubuntu-latest @@ -88,15 +94,14 @@ jobs: - thumbv6m-none-eabi backend_feature: - - - --features ristretto255-ciphersuite,ristretto255-u64 - - --features ristretto255-ciphersuite,ristretto255-u32 + - --features ristretto255-ciphersuite frontend_feature: - - --features danger - --features serde steps: - - uses: actions/checkout@v3 - - uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@v4 + - uses: hecrj/setup-rust-action@v2 - run: rustup target add ${{ matrix.target }} - run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} @@ -106,7 +111,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -120,7 +125,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets -- -D warnings + args: --all-features --all-targets -- -D warnings - name: Run cargo doc uses: actions-rs/cargo@v1 @@ -136,7 +141,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install nightly toolchain uses: actions-rs/toolchain@v1 @@ -157,7 +162,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/.crates.toml @@ -166,10 +171,10 @@ jobs: key: taplo - name: Install Taplo - run: cargo install taplo-cli + run: cargo install taplo-cli --locked - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run Taplo run: taplo fmt --check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 24fdd8c..5ae8001 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,9 +10,10 @@ jobs: strategy: matrix: os: [ubuntu-latest] + rust: [stable] steps: - - uses: hecrj/setup-rust-action@v1 + - uses: hecrj/setup-rust-action@v2 with: rust-version: ${{ matrix.rust }} - uses: actions/checkout@master diff --git a/.gitignore b/.gitignore index 088ba6b..3507e83 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +# Editors +.idea +.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e57b8e..8c8ddad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.1 (TBD) +* Backport all non-protocol-breaking changes from versions 0.5+ + * Fixes Rust 1.81+ compatibility, compatible with 0.4.0 (draft 11), incompatible with 0.5+ (final RFC) +* Updated dependencies + ## 0.4.0 (September 15, 2022) * Updated to be in sync with draft-irtf-cfrg-voprf-11, with the addition of the POPRF mode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 906df5e..a2aeae6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,4 +27,5 @@ outlined on that page and do not file a public issue. ## License By contributing to voprf, you agree that your contributions will be -licensed under the LICENSE file in the root directory of this source tree. +licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root +directory of this source tree. diff --git a/Cargo.toml b/Cargo.toml index f94a116..bba6601 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,56 +7,63 @@ keywords = ["oprf"] license = "MIT" name = "voprf" readme = "README.md" -repository = "https://github.com/novifinancial/voprf/" -rust-version = "1.57" -version = "0.4.0" +repository = "https://github.com/facebook/voprf/" +rust-version = "1.65" +version = "0.4.1" [features] alloc = [] danger = [] -default = ["ristretto255-ciphersuite", "ristretto255-u64", "serde"] -ristretto255 = ["curve25519-dalek", "generic-array/more_lengths"] -ristretto255-ciphersuite = ["ristretto255", "sha2"] -ristretto255-fiat-u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"] -ristretto255-fiat-u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"] -ristretto255-simd = ["curve25519-dalek/simd_backend", "ristretto255"] -ristretto255-u32 = ["curve25519-dalek/u32_backend", "ristretto255"] -ristretto255-u64 = ["curve25519-dalek/u64_backend", "ristretto255"] -serde = ["generic-array/serde", "serde_"] +default = ["ristretto255-ciphersuite", "dep:serde"] +ristretto255 = ["dep:curve25519-dalek", "generic-array/more_lengths"] +ristretto255-ciphersuite = ["ristretto255", "dep:sha2"] +serde = ["generic-array/serde", "dep:serde"] std = ["alloc"] [dependencies] -curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true } +curve25519-dalek = { version = "4", default-features = false, features = [ + "rand_core", + "zeroize", +], optional = true } derive-where = { version = "1", features = ["zeroize-on-drop"] } digest = "0.10" displaydoc = { version = "0.2", default-features = false } -elliptic-curve = { version = "0.12", features = [ +elliptic-curve = { version = "0.13", features = [ "hash2curve", "sec1", "voprf", ] } generic-array = "0.14" rand_core = { version = "0.6", default-features = false } -serde_ = { version = "1", package = "serde", default-features = false, features = [ +serde = { version = "1", default-features = false, features = [ "derive", ], optional = true } sha2 = { version = "0.10", default-features = false, optional = true } -subtle = { version = "2.3", default-features = false } -zeroize = { version = "1.5", default-features = false } +subtle = { version = "2.6", default-features = false } +zeroize = { version = "1.8", default-features = false } [dev-dependencies] generic-array = { version = "0.14", features = ["more_lengths"] } hex = "0.4" -json = "0.12" -p256 = { version = "0.11", default-features = false, features = [ +p256 = { version = "0.13", default-features = false, features = [ + "hash2curve", + "voprf", +] } +p384 = { version = "0.13", default-features = false, features = [ + "hash2curve", + "voprf", +] } +p521 = { version = "0.13.3", default-features = false, features = [ "hash2curve", "voprf", ] } proptest = "1" rand = "0.8" regex = "1" +serde_json = "1" sha2 = "0.10" [package.metadata.docs.rs] -features = ["danger", "std"] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] targets = [] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 09250ca..0000000 --- a/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -## License - -Licensed under either of - * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall -be dual licensed as above, without any additional terms or conditions. diff --git a/README.md b/README.md index 2751847..4b4ac98 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# voprf ![Build Status](https://github.com/novifinancial/voprf/workflows/Rust%20CI/badge.svg) +# voprf ![Build Status](https://github.com/facebook/voprf/workflows/Rust%20CI/badge.svg) An implementation of a (verifiable) oblivious pseudorandom function (VOPRF) A VOPRF is a verifiable oblivious pseudorandom function, a protocol between a client and a server. The regular (non-verifiable) OPRF is also supported in this implementation. @@ -16,12 +16,12 @@ Installation Add the following line to the dependencies of your `Cargo.toml`: ``` -voprf = "0.4" +voprf = "0.4.1" ``` ### Minimum Supported Rust Version -Rust **1.57** or higher. +Rust **1.65** or higher. Contributors ------------ @@ -32,4 +32,6 @@ To learn more about contributing to this project, [see this document](./CONTRIBU License ------- -This project is [licensed](./LICENSE) under either Apache 2.0 or MIT, at your option. +This project is dual-licensed under either the [MIT license](./LICENSE-MIT) +or the [Apache License, Version 2.0](./LICENSE-APACHE). +You may select, at your option, one of the above-listed licenses. diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index 734c9ca..0f080d2 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -1,14 +1,15 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Defines the CipherSuite trait to specify the underlying primitives for VOPRF use digest::core_api::BlockSizeUser; -use digest::{Digest, OutputSizeUser}; +use digest::{FixedOutput, HashMarker, OutputSizeUser}; use elliptic_curve::VoprfParameters; use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; @@ -22,7 +23,7 @@ where { /// The ciphersuite identifier as dictated by /// - const ID: u16; + const ID: &'static str; /// A finite cyclic group along with a point representation that allows some /// customization on how to hash an input to a curve point. See [`Group`]. @@ -30,17 +31,17 @@ where /// The main hash function to use (for HKDF computations and hashing /// transcripts). - type Hash: BlockSizeUser + Digest; + type Hash: BlockSizeUser + Default + FixedOutput + HashMarker; } impl CipherSuite for T where T: Group, - T::Hash: BlockSizeUser + Digest, + T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker, ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - const ID: u16 = T::ID; + const ID: &'static str = T::ID; type Group = T; diff --git a/src/common.rs b/src/common.rs index 6af1b95..a04056a 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,19 +1,21 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Common functionality between multiple OPRF modes. use core::convert::TryFrom; +use core::ops::Add; use derive_where::derive_where; use digest::core_api::BlockSizeUser; use digest::{Digest, Output, OutputSizeUser}; use generic_array::sequence::Concat; -use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U11, U2, U256}; +use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U2, U256, U9}; use generic_array::{ArrayLength, GenericArray}; use rand_core::{CryptoRng, RngCore}; use subtle::ConstantTimeEq; @@ -72,7 +74,7 @@ impl Mode { #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct BlindedElement( #[cfg_attr(feature = "serde", serde(with = "Element::"))] @@ -89,7 +91,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct EvaluationElement( #[cfg_attr(feature = "serde", serde(with = "Element::"))] @@ -106,7 +108,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct PreparedEvaluationElement(pub(crate) EvaluationElement) where @@ -120,7 +122,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct Proof where @@ -145,8 +147,8 @@ pub(crate) fn generate_proof( k: ::Scalar, a: ::Elem, b: ::Elem, - cs: impl Iterator::Elem> + ExactSizeIterator, - ds: impl Iterator::Elem> + ExactSizeIterator, + cs: impl ExactSizeIterator::Elem>, + ds: impl ExactSizeIterator::Elem>, mode: Mode, ) -> Result> where @@ -194,9 +196,9 @@ where &STR_CHALLENGE, ]; - let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); + let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); // This can't fail, the size of the `input` is known. - let c_scalar = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let c_scalar = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); let s_scalar = r - &(c_scalar * &k); Ok(Proof { c_scalar, s_scalar }) @@ -207,8 +209,8 @@ where pub(crate) fn verify_proof( a: ::Elem, b: ::Elem, - cs: impl Iterator::Elem> + ExactSizeIterator, - ds: impl Iterator::Elem> + ExactSizeIterator, + cs: impl ExactSizeIterator::Elem>, + ds: impl ExactSizeIterator::Elem>, proof: &Proof, mode: Mode, ) -> Result<()> @@ -254,9 +256,9 @@ where &STR_CHALLENGE, ]; - let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); + let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); // This can't fail, the size of the `input` is known. - let c = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let c = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); match c.ct_eq(&proof.c_scalar).into() { true => Ok(()), @@ -296,16 +298,16 @@ where let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?; // seedDST = "Seed-" || contextString - let seed_dst = GenericArray::from(STR_SEED).concat(create_context_string::(mode)); + let seed_dst = Dst::new::(STR_SEED, mode); // h1Input = I2OSP(len(Bm), 2) || Bm || // I2OSP(len(seedDST), 2) || seedDST // seed = Hash(h1Input) let seed = CS::Hash::new() - .chain_update(&elem_len) + .chain_update(elem_len) .chain_update(CS::Group::serialize_elem(b)) - .chain_update(i2osp_2_array(&seed_dst)) - .chain_update(seed_dst) + .chain_update(seed_dst.i2osp_2()) + .chain_update_multi(&seed_dst.as_dst()) .finalize(); let seed_len = i2osp_2_array(&seed); @@ -332,9 +334,9 @@ where &STR_COMPOSITE, ]; - let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); + let dst = Dst::new::(STR_HASH_TO_SCALAR, mode); // This can't fail, the size of the `input` is known. - let di = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let di = CS::Group::hash_to_scalar::(&h2_input, &dst.as_dst()).unwrap(); m = c * &di + &m; z = match k_option { Some(_) => z, @@ -365,8 +367,7 @@ where ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - let context_string = create_context_string::(mode); - let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string); + let dst = Dst::new::(STR_DERIVE_KEYPAIR, mode); let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; @@ -376,7 +377,7 @@ where // || contextString) let sk_s = CS::Group::hash_to_scalar::( &[seed, &info_len, info, &counter.to_be_bytes()], - &dst, + &dst.as_dst(), ) .map_err(|_| Error::DeriveKeyPair)?; @@ -388,7 +389,12 @@ where Err(Error::Protocol) } -/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`]. +/// Corresponds to DeriveKeyPair() function from the VOPRF specification. +/// +/// # Errors +/// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer +/// then `u16::MAX - 3`. +/// - [`Error::Protocol`] if the protocol fails and can't be completed. #[cfg(feature = "danger")] pub fn derive_key( seed: &[u8], @@ -450,8 +456,8 @@ where ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - CS::Group::hash_to_curve::(&[input], &dst).map_err(|_| Error::Input) + let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + CS::Group::hash_to_curve::(&[input], &dst.as_dst()).map_err(|_| Error::Input) } /// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF. @@ -492,16 +498,70 @@ where .finalize()) } -/// Generates the contextString parameter as defined in -/// -pub(crate) fn create_context_string(mode: Mode) -> GenericArray +pub(crate) struct Dst> { + dst_1: GenericArray, + dst_2: [u8; 2], +} + +impl> Dst { + pub(crate) fn new(par_1: T, mode: Mode) -> Self + where + T: Into>, + TL: ArrayLength + Add, + ::OutputSize: + IsLess + IsLessOrEqual<::BlockSize>, + { + let par_1 = par_1.into(); + // Generates the contextString parameter as defined in + // + let par_2 = GenericArray::from(STR_VOPRF).concat([mode.to_u8()].into()); + + // See + let cs_id_u16: u16 = match CS::ID { + "ristretto255-SHA512" => 0x0001, + "decaf448-SHAKE256" => 0x0002, + "P256-SHA256" => 0x0003, + "P384-SHA384" => 0x0004, + "P521-SHA512" => 0x0005, + _ => panic!("Incompatible ciphersuite: {}", CS::ID), + }; + + let dst_1 = par_1.concat(par_2); + let dst_2 = cs_id_u16.to_be_bytes(); + + assert!( + L::USIZE + 2 <= u16::MAX.into(), + "constructed DST longer then {}", + u16::MAX + ); + + Self { dst_1, dst_2 } + } + + pub(crate) fn as_dst(&self) -> [&[u8]; 2] { + [&self.dst_1, &self.dst_2] + } + + pub(crate) fn i2osp_2(&self) -> [u8; 2] { + u16::try_from(L::USIZE + 2).unwrap().to_be_bytes() + } +} + +trait DigestExt { + fn chain_update_multi(self, data: &[&[u8]]) -> Self; +} + +impl DigestExt for T where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>, + T: Digest, { - GenericArray::from(STR_VOPRF) - .concat([mode.to_u8()].into()) - .concat(CS::ID.to_be_bytes().into()) + fn chain_update_multi(mut self, datas: &[&[u8]]) -> Self { + for data in datas { + self.update(data) + } + + self + } } /////////////////////// diff --git a/src/error.rs b/src/error.rs index 2819edb..5e794bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Errors which are produced during an execution of the protocol diff --git a/src/group/elliptic_curve.rs b/src/group/elliptic_curve.rs index edbd958..2ecafda 100644 --- a/src/group/elliptic_curve.rs +++ b/src/group/elliptic_curve.rs @@ -1,17 +1,18 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. use digest::core_api::BlockSizeUser; -use digest::Digest; +use digest::{FixedOutput, HashMarker}; use elliptic_curve::group::cofactor::CofactorGroup; use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest}; use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}; use elliptic_curve::{ - AffinePoint, Field, FieldSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey, + AffinePoint, Field, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey, }; use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::GenericArray; @@ -24,32 +25,32 @@ impl Group for C where C: GroupDigest, ProjectivePoint: CofactorGroup + ToEncodedPoint, - FieldSize: ModulusSize, + FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, Scalar: FromOkm, { type Elem = ProjectivePoint; - type ElemLen = as ModulusSize>::CompressedPointSize; + type ElemLen = as ModulusSize>::CompressedPointSize; type Scalar = Scalar; - type ScalarLen = FieldSize; + type ScalarLen = FieldBytesSize; // 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(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual, { Self::hash_from_bytes::>(input, dst).map_err(|_| InternalError::Input) } // Implements the `HashToScalar()` function - fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual, { ::hash_to_scalar::>(input, dst) @@ -92,7 +93,7 @@ where #[cfg(test)] fn zero_scalar() -> Self::Scalar { - Scalar::::zero() + Scalar::::ZERO } fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { @@ -100,7 +101,7 @@ where } fn deserialize_scalar(scalar_bits: &[u8]) -> Result { - SecretKey::::from_be_bytes(scalar_bits) + SecretKey::::from_slice(scalar_bits) .map(|secret_key| *secret_key.to_nonzero_scalar()) .map_err(|_| Error::Deserialization) } diff --git a/src/group/mod.rs b/src/group/mod.rs index 132b78d..923ec9a 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Defines the Group trait to specify the underlying prime order group @@ -14,7 +15,7 @@ mod ristretto; use core::ops::{Add, Mul, Sub}; use digest::core_api::BlockSizeUser; -use digest::Digest; +use digest::{FixedOutput, HashMarker}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::{ArrayLength, GenericArray}; use rand_core::{CryptoRng, RngCore}; @@ -54,9 +55,9 @@ pub trait Group { /// # Errors /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// then [`u16::MAX`]. - fn hash_to_curve(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual; /// Hashes a slice of pseudo-random bytes to a scalar @@ -64,9 +65,9 @@ pub trait Group { /// # Errors /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// then [`u16::MAX`]. - fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual; /// Get the base point for the group diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index bf42d19..568e1b7 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -1,16 +1,17 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::Identity; use digest::core_api::BlockSizeUser; -use digest::Digest; +use digest::{FixedOutput, HashMarker}; use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; use generic_array::GenericArray; @@ -22,21 +23,17 @@ use crate::{Error, InternalError, Result}; /// [`Group`] implementation for Ristretto255. #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428 -#[cfg(feature = "ristretto255")] pub struct Ristretto255; #[cfg(feature = "ristretto255-ciphersuite")] impl crate::CipherSuite for Ristretto255 { - const ID: u16 = 0x0001; + const ID: &'static str = "ristretto255-SHA512"; type Group = Ristretto255; type Hash = sha2::Sha512; } -// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428 -#[cfg(feature = "ristretto255")] impl Group for Ristretto255 { type Elem = RistrettoPoint; @@ -48,9 +45,9 @@ impl Group for Ristretto255 { // Implements the `hash_to_ristretto255()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt - fn hash_to_curve(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual, { let mut uniform_bytes = GenericArray::<_, U64>::default(); @@ -63,9 +60,9 @@ impl Group for Ristretto255 { // Implements the `HashToScalar()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1 - fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where - H: Digest + BlockSizeUser, + H: BlockSizeUser + Default + FixedOutput + HashMarker, H::OutputSize: IsLess + IsLessOrEqual, { let mut uniform_bytes = GenericArray::<_, U64>::default(); @@ -90,11 +87,8 @@ impl Group for Ristretto255 { } fn deserialize_elem(element_bits: &[u8]) -> Result { - if element_bits.len() != 32 { - return Err(Error::Deserialization); - } - CompressedRistretto::from_slice(element_bits) + .map_err(|_| Error::Deserialization)? .decompress() .filter(|point| point != &RistrettoPoint::identity()) .ok_or(Error::Deserialization) @@ -104,7 +98,7 @@ impl Group for Ristretto255 { loop { let scalar = Scalar::random(rng); - if scalar != Scalar::zero() { + if scalar != Scalar::ZERO { break scalar; } } @@ -115,12 +109,12 @@ impl Group for Ristretto255 { } fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice { - scalar.ct_eq(&Scalar::zero()) + scalar.ct_eq(&Scalar::ZERO) } #[cfg(test)] fn zero_scalar() -> Self::Scalar { - Scalar::zero() + Scalar::ZERO } fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { @@ -131,8 +125,8 @@ impl Group for Ristretto255 { scalar_bits .try_into() .ok() - .and_then(Scalar::from_canonical_bytes) - .filter(|scalar| scalar != &Scalar::zero()) + .and_then(|bytes| Scalar::from_canonical_bytes(bytes).into()) + .filter(|scalar| scalar != &Scalar::ZERO) .ok_or(Error::Deserialization) } } diff --git a/src/group/tests.rs b/src/group/tests.rs index f12571f..58737ce 100644 --- a/src/group/tests.rs +++ b/src/group/tests.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Includes a series of tests for the group implementations @@ -15,6 +16,8 @@ use crate::{Error, Group, Result}; #[test] fn test_group_properties() -> Result<()> { use p256::NistP256; + use p384::NistP384; + use p521::NistP521; #[cfg(feature = "ristretto255")] { @@ -27,6 +30,12 @@ fn test_group_properties() -> Result<()> { test_identity_element_error::()?; test_zero_scalar_error::()?; + test_identity_element_error::()?; + test_zero_scalar_error::()?; + + test_identity_element_error::()?; + test_zero_scalar_error::()?; + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 00fbe88..81dd4ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,15 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! An implementation of a verifiable oblivious pseudorandom function (VOPRF) //! //! Note: This implementation is in sync with -//! [draft-irtf-cfrg-voprf-11](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-11.html), +//! [draft-irtf-cfrg-voprf-11](https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html), //! but this specification is subject to change, until the final version //! published by the IETF. //! @@ -532,24 +533,15 @@ //! a [`CipherSuite`]. //! //! - The `ristretto255` feature enables using [`Ristretto255`] as the -//! underlying group for the [Group] choice. A backend feature, which are -//! re-exported from [curve25519-dalek] and allow for selecting the -//! corresponding backend for the curve arithmetic used, has to be selected, -//! otherwise compilation will fail. The `ristretto255-u64` feature is -//! included as the default. Other features are mapped as `ristretto255-u32`, -//! `ristretto255-fiat-u64` and `ristretto255-fiat-u32`. Any `ristretto255-*` -//! backend feature will enable the `ristretto255` feature. -//! -//! - The `ristretto255-simd` feature is re-exported from [curve25519-dalek] and -//! enables parallel formulas, using either AVX2 or AVX512-IFMA. This will -//! automatically enable the `ristretto255-u64` feature and requires Rust -//! nightly. +//! underlying group for the [Group] choice. To select a specific backend see +//! the [curve25519-dalek] documentation. //! //! [curve25519-dalek]: -//! (https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) +//! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends) -#![cfg_attr(not(test), deny(unsafe_code))] #![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(not(test), deny(unsafe_code))] #![warn( clippy::cargo, clippy::missing_errors_doc, @@ -564,9 +556,6 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -#[cfg(feature = "serde")] -extern crate serde_ as serde; - mod ciphersuite; mod common; mod error; diff --git a/src/oprf.rs b/src/oprf.rs index 1fa73f0..710b748 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Contains the main OPRF API @@ -41,7 +42,7 @@ use crate::{CipherSuite, Error, Group, Result}; #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct OprfClient where @@ -59,7 +60,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct OprfServer where @@ -277,7 +278,7 @@ where .chain_update(input.as_ref()) .chain_update(elem_len) .chain_update(CS::Group::serialize_elem(unblinded_element)) - .chain_update(&STR_FINALIZE) + .chain_update(STR_FINALIZE) .finalize()) }) } @@ -291,11 +292,10 @@ where 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::common::{Dst, STR_HASH_TO_GROUP}; use crate::Group; fn prf( @@ -308,8 +308,8 @@ mod tests { ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); let res = point * &key; @@ -348,9 +348,8 @@ mod tests { .finalize(&input, &EvaluationElement(client_blind_result.message.0)) .unwrap(); - let dst = - GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(Mode::Oprf)); - let point = CS::Group::hash_to_curve::(&[&input], &dst).unwrap(); + let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); + let point = CS::Group::hash_to_curve::(&[&input], &dst.as_dst()).unwrap(); let res2 = finalize_after_unblind::(iter::once((input.as_ref(), point)), &[]) .next() .unwrap() @@ -427,6 +426,8 @@ mod tests { #[test] fn test_functionality() -> Result<()> { use p256::NistP256; + use p384::NistP384; + use p521::NistP521; #[cfg(feature = "ristretto255")] { @@ -447,6 +448,20 @@ mod tests { zeroize_oprf_client::(); zeroize_oprf_server::(); + base_retrieval::(); + base_inversion_unsalted::(); + server_evaluate::(); + + zeroize_oprf_client::(); + zeroize_oprf_server::(); + + base_retrieval::(); + base_inversion_unsalted::(); + server_evaluate::(); + + zeroize_oprf_client::(); + zeroize_oprf_server::(); + Ok(()) } } diff --git a/src/poprf.rs b/src/poprf.rs index 1dfabe7..a0fd531 100644 --- a/src/poprf.rs +++ b/src/poprf.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Contains the main POPRF API @@ -14,16 +15,14 @@ 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, - hash_to_group, i2osp_2, server_evaluate_hash_input, verify_proof, BlindedElement, - EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, - STR_INFO, + derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, + server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode, + PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO, }; #[cfg(feature = "serde")] use crate::serialization::serde::{Element, Scalar}; @@ -41,7 +40,7 @@ use crate::{CipherSuite, Error, Group, Result}; #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct PoprfClient where @@ -61,7 +60,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct PoprfServer where @@ -541,7 +540,7 @@ pub type PoprfServerBatchEvaluatePreparedEvaluationElements = Map< #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct PoprfPreparedTweak( #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] @@ -616,10 +615,9 @@ where let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let framed_info = [STR_INFO.as_slice(), &info_len, info]; - let dst = - GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(Mode::Poprf)); + let dst = Dst::new::(STR_HASH_TO_SCALAR, Mode::Poprf); // This can't fail, the size of the `input` is known. - let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); + let m = CS::Group::hash_to_scalar::(&framed_info, &dst.as_dst()).unwrap(); let t = CS::Group::base_elem() * &m; let tweaked_key = t + &pk; @@ -654,10 +652,9 @@ where let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let framed_info = [STR_INFO.as_slice(), &info_len, info]; - let dst = - GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(Mode::Poprf)); + let dst = Dst::new::(STR_HASH_TO_SCALAR, Mode::Poprf); // This can't fail, the size of the `input` is known. - let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); + let m = CS::Group::hash_to_scalar::(&framed_info, &dst.as_dst()).unwrap(); let t = sk + &m; @@ -723,7 +720,7 @@ where )?; Ok(blinds - .zip(messages.into_iter()) + .zip(messages) .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) } @@ -810,8 +807,8 @@ mod tests { { let t = compute_tweak::(key, Some(info)).unwrap(); - let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); // evaluatedElement = G.ScalarInverse(t) * blindedElement let res = point * &CS::Group::invert_scalar(t); @@ -864,10 +861,9 @@ mod tests { .blind_evaluate(&mut rng, &client_blind_result.message, Some(info)) .unwrap(); let wrong_pk = { - let dst = GenericArray::from(STR_HASH_TO_GROUP) - .concat(create_context_string::(Mode::Oprf)); + let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() }; let client_finalize_result = client_blind_result.state.finalize( input, @@ -970,6 +966,8 @@ mod tests { #[test] fn test_functionality() -> Result<()> { use p256::NistP256; + use p384::NistP384; + use p521::NistP521; #[cfg(feature = "ristretto255")] { @@ -990,6 +988,20 @@ mod tests { zeroize_verifiable_client::(); zeroize_verifiable_server::(); + verifiable_retrieval::(); + verifiable_bad_public_key::(); + verifiable_server_evaluate::(); + + zeroize_verifiable_client::(); + zeroize_verifiable_server::(); + + verifiable_retrieval::(); + verifiable_bad_public_key::(); + verifiable_server_evaluate::(); + + zeroize_verifiable_client::(); + zeroize_verifiable_server::(); + Ok(()) } } diff --git a/src/serialization.rs b/src/serialization.rs index 6a6fa60..7f1d38d 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Handles the serialization of each of the components used in the VOPRF //! protocol @@ -306,11 +307,11 @@ fn deserialize_scalar(input: &mut &[u8]) -> Result { } trait SliceExt { - fn take_ext(self: &mut &Self, take: usize) -> Option<&Self>; + fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self>; } impl SliceExt for [T] { - fn take_ext(self: &mut &Self, take: usize) -> Option<&Self> { + fn take_ext<'a>(self: &mut &'a Self, take: usize) -> Option<&'a Self> { if take > self.len() { return None; } @@ -389,6 +390,8 @@ mod test { } let _ = $item::::deserialize(&$bytes[..]); + let _ = $item::::deserialize(&$bytes[..]); + let _ = $item::::deserialize(&$bytes[..]); }; } diff --git a/src/tests/cfrg_vectors.rs b/src/tests/cfrg_vectors.rs index b7c26df..581518d 100644 --- a/src/tests/cfrg_vectors.rs +++ b/src/tests/cfrg_vectors.rs @@ -1,12 +1,13 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! The VOPRF test vectors taken from: -//! https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md +//! https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/dff20b461c0de23fcd521116f3d058cfa5b80b90/draft-irtf-cfrg-voprf.md pub(crate) const VECTORS: &str = r#" ## OPRF(ristretto255, SHA-512) diff --git a/src/tests/mock_rng.rs b/src/tests/mock_rng.rs index 3ccaee8..eea5442 100644 --- a/src/tests/mock_rng.rs +++ b/src/tests/mock_rng.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. use alloc::vec::Vec; use core::cmp::min; @@ -50,7 +51,7 @@ impl RngCore for CycleRng { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { let len = min(self.v.len(), dest.len()); - (&mut dest[..len]).copy_from_slice(&self.v[..len]); + dest[..len].copy_from_slice(&self.v[..len]); rotate_left(&mut self.v, len); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 56a7fc7..71e7089 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. mod cfrg_vectors; mod mock_rng; diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 313859c..5dd9913 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. use alloc::string::{String, ToString}; use alloc::vec::Vec; @@ -96,7 +97,7 @@ fn parse_params(input: &str) -> String { let key = iter.next().unwrap().split_whitespace().next().unwrap(); let val = iter.next().unwrap().split_whitespace().next().unwrap(); - param = format!(" \"{}\": \"{}", key, val); + param = format!(" \"{key}\": \"{val}"); } else { let s = line.trim().to_string(); if s.contains('~') || s.contains('#') { diff --git a/src/tests/test_cfrg_vectors.rs b/src/tests/test_cfrg_vectors.rs index 9fe23bf..ce34085 100644 --- a/src/tests/test_cfrg_vectors.rs +++ b/src/tests/test_cfrg_vectors.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. use alloc::string::String; use alloc::vec; @@ -14,7 +15,7 @@ use digest::core_api::BlockSizeUser; use digest::OutputSizeUser; use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; use generic_array::ArrayLength; -use json::JsonValue; +use serde_json::Value; use crate::tests::mock_rng::CycleRng; use crate::tests::parser::*; @@ -40,7 +41,7 @@ struct VOPRFTestVectorParameters { output: Vec>, } -fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters { +fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters { VOPRFTestVectorParameters { seed: decode(values, "Seed"), sksm: decode(values, "skSm"), @@ -57,18 +58,18 @@ fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters { } } -fn decode(values: &JsonValue, key: &str) -> Vec { +fn decode(values: &Value, key: &str) -> Vec { values[key] .as_str() - .and_then(|s| hex::decode(&s).ok()) + .and_then(|s| hex::decode(s).ok()) .unwrap_or_default() } -fn decode_vec(values: &JsonValue, key: &str) -> Vec> { +fn decode_vec(values: &Value, key: &str) -> Vec> { let s = values[key].as_str().unwrap(); let res = match s.contains(',') { - true => Some(s.split(',').map(|x| hex::decode(&x).unwrap()).collect()), - false => Some(vec![hex::decode(&s).unwrap()]), + true => Some(s.split(',').map(|x| hex::decode(x).unwrap()).collect()), + false => Some(vec![hex::decode(s).unwrap()]), }; res.unwrap() } @@ -76,8 +77,10 @@ fn decode_vec(values: &JsonValue, key: &str) -> Vec> { macro_rules! json_to_test_vectors { ( $v:ident, $cs:expr, $mode:expr ) => { $v[$cs][$mode] - .members() - .map(|x| populate_test_vectors(&x)) + .as_array() + .into_iter() + .flatten() + .map(populate_test_vectors) .collect::>() }; } @@ -85,8 +88,10 @@ macro_rules! json_to_test_vectors { #[test] fn test_vectors() -> Result<()> { use p256::NistP256; + use p384::NistP384; + use p521::NistP521; - let rfc = json::parse(rfc_to_json(super::cfrg_vectors::VECTORS).as_str()) + let rfc: Value = serde_json::from_str(rfc_to_json(super::cfrg_vectors::VECTORS).as_str()) .expect("Could not parse json"); #[cfg(feature = "ristretto255")] @@ -157,6 +162,60 @@ fn test_vectors() -> Result<()> { test_poprf_finalize::(&p256_poprf_tvs)?; test_poprf_evaluate::(&p256_poprf_tvs)?; + let p384_oprf_tvs = + json_to_test_vectors!(rfc, String::from("P-384, SHA-384"), String::from("OPRF")); + assert_ne!(p384_oprf_tvs.len(), 0); + test_oprf_seed_to_key::(&p384_oprf_tvs)?; + test_oprf_blind::(&p384_oprf_tvs)?; + test_oprf_blind_evaluate::(&p384_oprf_tvs)?; + test_oprf_finalize::(&p384_oprf_tvs)?; + test_oprf_evaluate::(&p384_oprf_tvs)?; + + let p384_voprf_tvs = + json_to_test_vectors!(rfc, String::from("P-384, SHA-384"), String::from("VOPRF")); + assert_ne!(p384_voprf_tvs.len(), 0); + test_voprf_seed_to_key::(&p384_voprf_tvs)?; + test_voprf_blind::(&p384_voprf_tvs)?; + test_voprf_blind_evaluate::(&p384_voprf_tvs)?; + test_voprf_finalize::(&p384_voprf_tvs)?; + test_voprf_evaluate::(&p384_voprf_tvs)?; + + let p384_poprf_tvs = + json_to_test_vectors!(rfc, String::from("P-384, SHA-384"), String::from("POPRF")); + assert_ne!(p384_poprf_tvs.len(), 0); + test_poprf_seed_to_key::(&p384_poprf_tvs)?; + test_poprf_blind::(&p384_poprf_tvs)?; + test_poprf_blind_evaluate::(&p384_poprf_tvs)?; + test_poprf_finalize::(&p384_poprf_tvs)?; + test_poprf_evaluate::(&p384_poprf_tvs)?; + + let p521_oprf_tvs = + json_to_test_vectors!(rfc, String::from("P-521, SHA-512"), String::from("OPRF")); + assert_ne!(p521_oprf_tvs.len(), 0); + test_oprf_seed_to_key::(&p521_oprf_tvs)?; + test_oprf_blind::(&p521_oprf_tvs)?; + test_oprf_blind_evaluate::(&p521_oprf_tvs)?; + test_oprf_finalize::(&p521_oprf_tvs)?; + test_oprf_evaluate::(&p521_oprf_tvs)?; + + let p521_voprf_tvs = + json_to_test_vectors!(rfc, String::from("P-521, SHA-512"), String::from("VOPRF")); + assert_ne!(p521_voprf_tvs.len(), 0); + test_voprf_seed_to_key::(&p521_voprf_tvs)?; + test_voprf_blind::(&p521_voprf_tvs)?; + test_voprf_blind_evaluate::(&p521_voprf_tvs)?; + test_voprf_finalize::(&p521_voprf_tvs)?; + test_voprf_evaluate::(&p521_voprf_tvs)?; + + let p521_poprf_tvs = + json_to_test_vectors!(rfc, String::from("P-521, SHA-512"), String::from("POPRF")); + assert_ne!(p521_poprf_tvs.len(), 0); + test_poprf_seed_to_key::(&p521_poprf_tvs)?; + test_poprf_blind::(&p521_poprf_tvs)?; + test_poprf_blind_evaluate::(&p521_poprf_tvs)?; + test_poprf_finalize::(&p521_poprf_tvs)?; + test_poprf_evaluate::(&p521_poprf_tvs)?; + Ok(()) } diff --git a/src/voprf.rs b/src/voprf.rs index 4df3b05..cbbafb3 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -1,9 +1,10 @@ -// Copyright (c) Facebook, Inc. and its affiliates. +// Copyright (c) Meta Platforms, Inc. and affiliates. // -// This source code is licensed under both the MIT license found in the -// LICENSE-MIT file in the root directory of this source tree and the Apache +// This source code is dual-licensed under either the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory -// of this source tree. +// of this source tree. You may select, at your option, one of the above-listed +// licenses. //! Contains the main VOPRF API @@ -39,7 +40,7 @@ use crate::{CipherSuite, Error, Group, Result}; #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct VoprfClient where @@ -59,7 +60,7 @@ where #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde", bound = "") + serde(bound = "") )] pub struct VoprfServer where @@ -160,7 +161,7 @@ where /// /// The resulting messages can each fail individually with [`Error::Input`] /// if the `input` is empty or longer then [`u16::MAX`]. - pub fn batch_finalize<'a, I: 'a, II, IC, IM>( + pub fn batch_finalize<'a, I, II, IC, IM>( inputs: &'a II, clients: &'a IC, messages: &'a IM, @@ -169,7 +170,7 @@ where ) -> Result> where CS: 'a, - I: AsRef<[u8]>, + I: 'a + AsRef<[u8]>, &'a II: 'a + IntoIterator, <&'a II as IntoIterator>::IntoIter: ExactSizeIterator, &'a IC: 'a + IntoIterator>, @@ -536,7 +537,7 @@ where )?; Ok(blinds - .zip(messages.into_iter()) + .zip(messages) .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) } @@ -587,13 +588,12 @@ mod tests { use ::alloc::vec; use ::alloc::vec::Vec; - use generic_array::sequence::Concat; use generic_array::typenum::Sum; use generic_array::ArrayLength; use rand::rngs::OsRng; use super::*; - use crate::common::{create_context_string, STR_HASH_TO_GROUP}; + use crate::common::{Dst, STR_HASH_TO_GROUP}; use crate::Group; fn prf( @@ -605,8 +605,8 @@ mod tests { ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); let res = point * &key; @@ -718,10 +718,9 @@ mod tests { .unwrap(); let messages: Vec<_> = messages.collect(); let wrong_pk = { - let dst = GenericArray::from(STR_HASH_TO_GROUP) - .concat(create_context_string::(Mode::Oprf)); + let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() }; let client_finalize_result = VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk); @@ -739,10 +738,9 @@ mod tests { let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let wrong_pk = { - let dst = GenericArray::from(STR_HASH_TO_GROUP) - .concat(create_context_string::(Mode::Oprf)); + let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() }; let client_finalize_result = client_blind_result.state.finalize( input, @@ -837,6 +835,8 @@ mod tests { #[test] fn test_functionality() -> Result<()> { use p256::NistP256; + use p384::NistP384; + use p521::NistP521; #[cfg(feature = "ristretto255")] { @@ -861,6 +861,24 @@ mod tests { zeroize_voprf_client::(); zeroize_voprf_server::(); + verifiable_retrieval::(); + verifiable_batch_retrieval::(); + verifiable_bad_public_key::(); + verifiable_batch_bad_public_key::(); + verifiable_server_evaluate::(); + + zeroize_voprf_client::(); + zeroize_voprf_server::(); + + verifiable_retrieval::(); + verifiable_batch_retrieval::(); + verifiable_bad_public_key::(); + verifiable_batch_bad_public_key::(); + verifiable_server_evaluate::(); + + zeroize_voprf_client::(); + zeroize_voprf_server::(); + Ok(()) } }