diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..caf0ba4 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,97 @@ +name: Rust CI + +on: [ push, pull_request ] + +concurrency: + group: ci-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + fmt: + name: cargo fmt + runs-on: linux_amd64 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Run cargo fmt + run: cargo fmt --all -- --check + + clippy: + name: cargo clippy + runs-on: linux_amd64 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run cargo clippy + run: cargo clippy --all-features --all-targets -- -D warnings + - name: Run cargo doc + run: cargo doc --no-deps --document-private-items --features danger,std + env: + RUSTDOCFLAGS: -D warnings + + test: + name: test (${{ matrix.toolchain }} / ${{ matrix.backend_feature || 'no backend' }} / ${{ matrix.frontend_feature || 'no frontend' }}) + runs-on: linux_amd64 + strategy: + fail-fast: false + matrix: + backend_feature: + - --features ristretto255-ciphersuite + - "" + frontend_feature: + - "" + - --features danger + - --features serde + toolchain: + - stable + - "1.87.0" + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: dtolnay/rust-toolchain@${{ matrix.toolchain }} + - name: Run cargo test + run: cargo test --no-default-features --lib --tests ${{ matrix.backend_feature }} + - name: Run cargo test with alloc + run: cargo test --no-default-features --lib --tests ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} --features alloc + - name: Run cargo test with std + run: cargo test --no-default-features --lib --tests ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} --features std + - name: Run cargo test with all features + run: cargo test --all-features + + build-no-std: + name: no-std (${{ matrix.target }} / ${{ matrix.backend_feature || 'no backend' }}) + runs-on: linux_amd64 + strategy: + fail-fast: false + matrix: + target: + - wasm32-unknown-unknown + - thumbv6m-none-eabi + backend_feature: + - "" + - --features ristretto255-ciphersuite + frontend_feature: + - "" + - --features danger + - --features serde + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - name: Build no-std + run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }} + + audit: + name: cargo audit + runs-on: linux_amd64 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-audit + run: cargo install cargo-audit + - name: Run cargo audit + run: cargo audit -D warnings \ No newline at end of file diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..dd25508 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,26 @@ +name: Publish + +on: + release: + types: [ published ] + +jobs: + publish: + runs-on: linux_amd64 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: dtolnay/rust-toolchain@stable + + - name: Login to crates.io + run: cargo login $CRATES_IO_TOKEN + env: + CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + + - name: Dry run publish + run: cargo publish --dry-run --manifest-path Cargo.toml + + - name: Publish + run: cargo publish --manifest-path Cargo.toml + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a66f37b..77faa66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.6.0-rc.0 (June 27, 2026) +* MSRV bumped to 1.87 +* Migrated from `elliptic-curve 0.13` to `0.14` +* Replaced `generic-array` with `hybrid-array` +* Updated `digest` dependency to 0.11 +* Updated `rand_core` dependency to 0.10 +* Updated `rand` dependency to 0.10 +* Updated `sha2` dependency to 0.11 +* Updated `p256`, `p384`, `p521` dependencies to 0.14.0-rc +* Replaced `elliptic-curve/hash2curve` feature with standalone `hash2curve 0.14` crate +* Updated `hash_to_scalar` to use `MapToCurve::Length` as OKM length per RFC 9380, replacing the removed `GroupDigest::hash_to_scalar` method +* Updated `random_scalar` to consume exactly `ScalarLen` bytes per attempt, adapting to the new `rand_core 0.10` API +* Added `OkmLen` associated type to `Group` trait + ## 0.6.0-pre.1 (April 6, 2026) * MSRV bumped to 1.85 * Updated rand_core dependency to 0.9 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index f049d4c..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when -an individual is representing the project or its community in public spaces. -Examples of representing a project or community include using an official -project e-mail address, posting via an official social media account, or acting -as an appointed representative at an online or offline event. Representation of -a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at . All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2aeae6..f192ad6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,29 +2,10 @@ We want to make contributing to this project as easy and transparent as possible. -## Pull Requests -We actively welcome your pull requests. - -1. Fork the repo and create your branch from `main`. -2. If you've added code that should be tested, add tests. -3. If you've changed APIs, update the documentation. -4. Ensure the test suite passes. -5. If you haven't already, complete the Contributor License Agreement ("CLA"). - -## Contributor License Agreement ("CLA") -In order to accept your pull request, we need you to submit a CLA. You only need -to do this once to work on any of Facebook's open source projects. - -Complete your CLA here: - ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe -disclosure of security bugs. In those cases, please go through the process -outlined on that page and do not file a public issue. - ## License By contributing to voprf, you agree that your contributions will be licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root diff --git a/Cargo.toml b/Cargo.toml index 2973f27..0e48c82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,68 +2,66 @@ authors = ["Kevin Lewi "] categories = ["no-std", "algorithms", "cryptography"] description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)" -edition = "2021" +edition = "2024" keywords = ["oprf"] license = "MIT" -name = "voprf" +name = "voprf-vexahub" readme = "README.md" repository = "https://github.com/facebook/voprf/" -rust-version = "1.85" -version = "0.6.0-pre.1" +rust-version = "1.87" +version = "0.6.0-rc.0" [features] alloc = [] danger = [] +doctest = ["dep:p256", "dep:sha2"] default = ["ristretto255-ciphersuite", "dep:serde"] ristretto255 = ["dep:curve25519-dalek"] ristretto255-ciphersuite = ["ristretto255", "dep:sha2"] -serde = ["curve25519-dalek?/serde", "generic-array/serde", "dep:serde"] +serde = ["curve25519-dalek?/serde", "hybrid-array/serde", "dep:serde"] std = ["alloc"] [dependencies] -curve25519-dalek = { version = "4", default-features = false, features = [ - "rand_core", - "zeroize", -], 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" +digest = "0.11" displaydoc = { version = "0.2", default-features = false } -elliptic-curve = { version = "0.13", features = [ - "hash2curve", - "sec1", - "voprf", +elliptic-curve = { version = "0.14", features = [ + "sec1", ] } -generic-array = "1" -rand_core = { version = "0.9", default-features = false } +hash2curve = "0.14" +hybrid-array = "0.4" +rand_core = { version = "0.10", default-features = false, features = [] } serde = { version = "1", default-features = false, features = [ - "derive", + "derive", ], optional = true } -sha2 = { version = "0.10", default-features = false, optional = true } +sha2 = { version = "0.11", default-features = false, optional = true } +p256 = { version = "0.14.0-rc", default-features = false, features = ["hash2curve", "oprf"], optional = true } subtle = { version = "2.6", default-features = false } zeroize = { version = "1.5", default-features = false } [dev-dependencies] -generic-array = { version = "1" } hex = "0.4" -p256 = { version = "0.13", default-features = false, features = [ - "hash2curve", - "voprf", +p256 = { version = "0.14.0-rc", default-features = false, features = [ + "hash2curve", + "oprf", ] } -p384 = { version = "0.13", default-features = false, features = [ - "hash2curve", - "voprf", +p384 = { version = "0.14.0-rc", default-features = false, features = [ + "hash2curve", + "oprf", ] } -p521 = { version = "0.13.3", default-features = false, features = [ - "hash2curve", - "voprf", +p521 = { version = "0.14.0-rc", default-features = false, features = [ + "hash2curve", + "oprf", ] } proptest = "1" -rand = "0.9" +rand = "0.10" regex = "1" serde_json = "1" -sha2 = "0.10" +sha2 = "0.11" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] targets = [] +features = ["doctest"] diff --git a/README.md b/README.md index 887661d..5838c17 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 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. @@ -8,7 +8,7 @@ This implementation is based on [RFC 9497](https://www.rfc-editor.org/rfc/rfc949 Documentation ------------- -The API can be found [here](https://docs.rs/voprf/) along with an example for usage. +The API can be found [here](https://docs.rs/voprf-vexahub/) along with an example for usage. Installation ------------ @@ -16,12 +16,12 @@ Installation Add the following line to the dependencies of your `Cargo.toml`: ``` -voprf = "0.6.0-pre.1" +voprf = { package = "voprf-vexahub", version = "0.6.0-rc.0" } ``` ### Minimum Supported Rust Version -Rust **1.85** or higher. +Rust **1.87** or higher. Contributors ------------ diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..510e947 --- /dev/null +++ b/renovate.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "dependencyDashboard": true, + "osvVulnerabilityAlerts": true, + "rangeStrategy": "auto", + "packageRules": [ + { + "matchManagers": [ + "cargo" + ], + "groupName": "rust deps" + }, + { + "matchManagers": [ + "cargo" + ], + "matchUpdateTypes": [ + "major" + ], + "automerge": false + } + ], + "lockFileMaintenance": { + "enabled": true + }, + "configMigration": true +} \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index faa263a..43d4840 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,8 +1 @@ -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 diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index c3a580a..afb5466 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -8,23 +8,26 @@ //! Defines the CipherSuite trait to specify the underlying primitives for VOPRF -use digest::core_api::BlockSizeUser; -use digest::{FixedOutput, HashMarker, OutputSizeUser}; -use elliptic_curve::VoprfParameters; -use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; -use generic_array::ArrayLength; - use crate::Group; +use core::ops::Mul; +use digest::block_api::BlockSizeUser; +use digest::typenum::{IsLess, IsLessOrEqual, U256}; +use digest::{Digest, FixedOutput, HashMarker, OutputSizeUser}; +use hybrid_array::ArraySize; +use hybrid_array::typenum::{IsGreaterOrEqual, Prod, True, U2}; /// Configures the underlying primitives used in VOPRF pub trait CipherSuite where - ::OutputSize: - ArrayLength + IsLess + IsLessOrEqual<::BlockSize>, + ::SecurityLevel: Mul, + ::OutputSize: ArraySize + + IsLess + + IsLessOrEqual<::BlockSize, Output = True> + + IsGreaterOrEqual::SecurityLevel, U2>, Output = True>, { /// The ciphersuite identifier as dictated by /// - const ID: &'static str; + const ID: &'static [u8]; /// A finite cyclic group along with a point representation that allows some /// customization on how to hash an input to a curve point. See [`Group`]. @@ -32,19 +35,5 @@ where /// The main hash function to use (for HKDF computations and hashing /// transcripts). - type Hash: BlockSizeUser + Default + FixedOutput + HashMarker; -} - -impl CipherSuite for T -where - T: Group, - T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker, - ::OutputSize: - ArrayLength + IsLess + IsLessOrEqual<::BlockSize>, -{ - const ID: &'static str = T::ID; - - type Group = T; - - type Hash = T::Hash; + type Hash: Digest + BlockSizeUser + Default + FixedOutput + HashMarker; } diff --git a/src/common.rs b/src/common.rs index 05e322b..ba0fc9f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -13,10 +13,9 @@ use core::ops::Add; use derive_where::derive_where; use digest::{Digest, Output, OutputSizeUser}; -use generic_array::sequence::Concat; -use generic_array::typenum::{IsLess, Unsigned, U2, U256, U9}; -use generic_array::{ArrayLength, GenericArray}; -use rand_core::{TryCryptoRng, TryRngCore}; +use hybrid_array::typenum::{IsLess, U2, U9, U256, Unsigned}; +use hybrid_array::{Array, ArrayN, ArraySize}; +use rand_core::{TryCryptoRng, TryRng}; use subtle::ConstantTimeEq; #[cfg(feature = "serde")] @@ -29,14 +28,14 @@ use crate::{CipherSuite, Error, Group, InternalError, Result}; /////////////// 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_SEED: ArrayN = Array(*b"Seed-"); +pub(crate) const STR_DERIVE_KEYPAIR: ArrayN = Array(*b"DeriveKeyPair"); pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite"; pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge"; pub(crate) const STR_INFO: [u8; 4] = *b"Info"; pub(crate) const STR_OPRF: [u8; 7] = *b"OPRFV1-"; -pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-"; -pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-"; +pub(crate) const STR_HASH_TO_SCALAR: ArrayN = Array(*b"HashToScalar-"); +pub(crate) const STR_HASH_TO_GROUP: ArrayN = Array(*b"HashToGroup-"); /// Determines the mode of operation (either base mode or verifiable mode). This /// is only used for custom implementations for [`Group`]. @@ -128,7 +127,7 @@ pub struct Proof { /// Can only fail with [`Error::Batch`]. #[allow(clippy::many_single_char_names)] -pub(crate) fn generate_proof( +pub(crate) fn generate_proof( rng: &mut R, k: ::Scalar, a: ::Elem, @@ -178,7 +177,7 @@ pub(crate) fn generate_proof( &STR_CHALLENGE, ]; - let dst = Dst::new::(STR_HASH_TO_SCALAR, 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.as_dst()).unwrap(); let s_scalar = r - &(c_scalar * &k); @@ -234,7 +233,7 @@ pub(crate) fn verify_proof( &STR_CHALLENGE, ]; - let dst = Dst::new::(STR_HASH_TO_SCALAR, 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.as_dst()).unwrap(); @@ -272,7 +271,7 @@ fn compute_composites< let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?; // seedDST = "Seed-" || contextString - let seed_dst = Dst::new::(STR_SEED, mode); + let seed_dst = Dst::new::(STR_SEED, mode); // h1Input = I2OSP(len(Bm), 2) || Bm || // I2OSP(len(seedDST), 2) || seedDST @@ -308,7 +307,7 @@ fn compute_composites< &STR_COMPOSITE, ]; - let dst = Dst::new::(STR_HASH_TO_SCALAR, 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.as_dst()).unwrap(); m = c * &di + &m; @@ -337,7 +336,7 @@ pub(crate) fn derive_key_internal( info: &[u8], mode: Mode, ) -> Result<::Scalar, Error> { - let dst = Dst::new::(STR_DERIVE_KEYPAIR, mode); + let dst = Dst::new::(STR_DERIVE_KEYPAIR, mode); let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; @@ -410,7 +409,7 @@ pub(crate) fn hash_to_group( input: &[u8], mode: Mode, ) -> Result<::Elem> { - let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + let dst = Dst::new::(STR_HASH_TO_GROUP, mode); CS::Group::hash_to_curve::(&[input], &dst.as_dst()).map_err(|_| Error::Input) } @@ -419,7 +418,7 @@ pub(crate) fn hash_to_group( pub(crate) fn server_evaluate_hash_input( input: &[u8], info: Option<&[u8]>, - issued_element: GenericArray::Group as Group>::ElemLen>, + issued_element: Array::Group as Group>::ElemLen>, ) -> Result> { // OPRF & VOPRF // hashInput = I2OSP(len(input), 2) || input || @@ -448,24 +447,22 @@ pub(crate) fn server_evaluate_hash_input( .finalize()) } -pub(crate) struct Dst { - dst_1: GenericArray, - dst_2: &'static str, +pub(crate) struct Dst { + dst_1: Array, + dst_2: &'static [u8], } -impl Dst { - pub(crate) fn new(par_1: T, mode: Mode) -> Self +impl Dst { + pub(crate) fn new(par_1: Array, mode: Mode) -> Self where CS: CipherSuite, - T: Into>, - TL: ArrayLength + Add, + TL: ArraySize + Add, { - let par_1 = par_1.into(); // Generates the contextString parameter as defined in // - let par_2 = GenericArray::from(STR_OPRF) - .concat([mode.to_u8()].into()) - .concat([b'-'].into()); + let par_2 = ArrayN::::from(STR_OPRF) + .concat(ArrayN::::from([mode.to_u8()])) + .concat(ArrayN::::from([b'-'])); let dst_1 = par_1.concat(par_2); let dst_2 = CS::ID; @@ -480,7 +477,7 @@ impl Dst { } pub(crate) fn as_dst(&self) -> [&[u8]; 2] { - [&self.dst_1, self.dst_2.as_bytes()] + [&self.dst_1, self.dst_2] } pub(crate) fn i2osp_2(&self) -> [u8; 2] { @@ -518,6 +515,6 @@ pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> { .map_err(|_| InternalError::I2osp) } -pub(crate) fn i2osp_2_array>() -> GenericArray { +pub(crate) fn i2osp_2_array>() -> Array { L::U16.to_be_bytes().into() } diff --git a/src/group/elliptic_curve.rs b/src/group/elliptic_curve.rs index b06028c..ccb3b4f 100644 --- a/src/group/elliptic_curve.rs +++ b/src/group/elliptic_curve.rs @@ -6,21 +6,20 @@ // of this source tree. You may select, at your option, one of the above-listed // licenses. -use core::num::NonZeroU32; -use core::ops::Add; - -use digest::core_api::BlockSizeUser; +use core::ops::{Add, Mul}; +use digest::block_api::BlockSizeUser; +use digest::typenum::{IsLess, IsLessOrEqual, U256}; use digest::{FixedOutput, HashMarker}; use elliptic_curve::group::cofactor::CofactorGroup; -use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest}; -use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}; +use elliptic_curve::sec1::{FromSec1Point, ModulusSize, ToSec1Point}; use elliptic_curve::{ AffinePoint, Field, FieldBytes, FieldBytesSize, Group as _, ProjectivePoint, PublicKey, Scalar, SecretKey, }; -use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; -use generic_array::{ArrayLength, GenericArray}; -use rand_core::{TryCryptoRng, TryRngCore}; +use hash2curve::{ExpandMsgXmd, GroupDigest, MapToCurve, hash_to_scalar}; +use hybrid_array::typenum::{IsGreaterOrEqual, Prod, Sum, True, U2}; +use hybrid_array::{Array, ArraySize}; +use rand_core::TryCryptoRng; use super::Group; use crate::{Error, InternalError, Result}; @@ -30,19 +29,24 @@ type ScalarLen = FieldBytesSize; impl Group for C where - C: GroupDigest, - ProjectivePoint: CofactorGroup + ToEncodedPoint, + C: GroupDigest + MapToCurve, + C::SecurityLevel: Mul, + C::SecurityLevel: ArraySize, + >::Output: ArraySize, + ProjectivePoint: CofactorGroup + ToSec1Point, ScalarLen: ModulusSize, - ScalarLen: ArrayLength, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - Scalar: FromOkm, + ScalarLen: ArraySize, + ScalarLen: hybrid_array::typenum::NonZero, + Scalar: elliptic_curve::ops::Reduce>>, + Scalar: elliptic_curve::ops::Reduce::Length>>, + AffinePoint: FromSec1Point + ToSec1Point, // `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen` ScalarLen: Add>, - Sum, ElemLen>: ArrayLength, + Sum, ElemLen>: ArraySize, // `ProofLen` ScalarLen: Add>, - Sum, ScalarLen>: ArrayLength, - ElemLen: ArrayLength, + Sum, ScalarLen>: ArraySize, + ElemLen: ArraySize, { type Elem = ProjectivePoint; @@ -52,23 +56,25 @@ where type ScalarLen = ScalarLen; + type SecurityLevel = C::SecurityLevel; + + type OkmLen = ::Length; + // Implements the `hash_to_curve()` function from // https://www.rfc-editor.org/rfc/rfc9380.html#section-3 - fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result - where - H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual, - { - Self::hash_from_bytes::>(input, dst).map_err(|_| InternalError::Input) + fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result { + Self::hash_from_bytes(input, dst).map_err(|_| InternalError::Input) } // Implements the `HashToScalar()` function fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual, + H::OutputSize: IsLess + IsLessOrEqual, + C::SecurityLevel: Mul, + H::OutputSize: IsGreaterOrEqual, Output = True>, { - ::hash_to_scalar::>(input, dst) + hash_to_scalar::, ::Length>(input, dst) .map_err(|_| InternalError::Input) } @@ -80,10 +86,10 @@ where ProjectivePoint::::identity() } - fn serialize_elem(elem: Self::Elem) -> GenericArray { - let bytes = elem.to_encoded_point(true); + fn serialize_elem(elem: Self::Elem) -> Array { + let bytes = elem.to_sec1_point(true); let bytes = bytes.as_bytes(); - let mut result = GenericArray::default(); + let mut result = Array::default(); result[..bytes.len()].copy_from_slice(bytes); result } @@ -94,8 +100,16 @@ where .map_err(|_| Error::Deserialization) } - fn random_scalar(rng: &mut R) -> Result { - Ok(*SecretKey::::random(&mut CompatRng(rng)).to_nonzero_scalar()) + fn random_scalar(rng: &mut R) -> Result { + loop { + let mut bytes = FieldBytes::::default(); + + rng.try_fill_bytes(&mut bytes).map_err(|_| Error::Rng)?; + + if let Ok(key) = SecretKey::::from_slice(&bytes) { + return Ok(*key.to_nonzero_scalar()); + } + } } fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar { @@ -111,9 +125,9 @@ where Scalar::::ZERO } - fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { + fn serialize_scalar(scalar: Self::Scalar) -> Array { let bytes: FieldBytes = scalar.into(); - let mut result = GenericArray::::default(); + let mut result = Array::::default(); result.as_mut_slice().copy_from_slice(bytes.as_ref()); result } @@ -124,42 +138,3 @@ where .map_err(|_| Error::Deserialization) } } - -/// Adapter allowing `rand_core 0.9` RNGs to satisfy the `elliptic_curve` 0.13 -/// requirement for `rand_core 0.6` traits. -/// -/// TODO #150: Remove this adapter when `elliptic_curve` migrates to `rand_core -/// 0.9`. -struct CompatRng<'a, R>(&'a mut R); - -impl<'a, R> elliptic_curve::rand_core::RngCore for CompatRng<'a, R> -where - R: TryRngCore, -{ - fn next_u32(&mut self) -> u32 { - self.0.try_next_u32().expect("RNG failure") - } - - fn next_u64(&mut self) -> u64 { - self.0.try_next_u64().expect("RNG failure") - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0 - .try_fill_bytes(dest) - .expect("RNG failure while filling bytes"); - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), elliptic_curve::rand_core::Error> { - self.0.try_fill_bytes(dest).map_err(|_| compat_error())?; - Ok(()) - } -} - -impl<'a, R> elliptic_curve::rand_core::CryptoRng for CompatRng<'a, R> where R: TryCryptoRng {} - -fn compat_error() -> elliptic_curve::rand_core::Error { - let code = NonZeroU32::new(elliptic_curve::rand_core::Error::CUSTOM_START) - .expect("CUSTOM_START must be non-zero"); - elliptic_curve::rand_core::Error::from(code) -} diff --git a/src/group/mod.rs b/src/group/mod.rs index c089c92..515379e 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -14,11 +14,11 @@ mod ristretto; use core::ops::{Add, Mul, Sub}; -use digest::core_api::BlockSizeUser; +use digest::block_api::BlockSizeUser; use digest::{FixedOutput, HashMarker}; -use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; -use generic_array::{ArrayLength, GenericArray}; -use rand_core::{TryCryptoRng, TryRngCore}; +use hybrid_array::typenum::{IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, Sum, True, U2, U256}; +use hybrid_array::{Array, ArraySize}; +use rand_core::{TryCryptoRng, TryRng}; #[cfg(feature = "ristretto255")] pub use ristretto::Ristretto255; use subtle::{Choice, ConstantTimeEq}; @@ -32,10 +32,10 @@ pub trait Group where // `VoprfClientLen`, `PoprfClientLen`, `VoprfServerLen`, `PoprfServerLen` Self::ScalarLen: Add, - Sum: ArrayLength, + Sum: ArraySize, // `ProofLen` Self::ScalarLen: Add, - Sum: ArrayLength, + Sum: ArraySize, { /// The type of group elements type Elem: ConstantTimeEq @@ -45,7 +45,7 @@ where + for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>; /// The byte length necessary to represent group elements - type ElemLen: ArrayLength + 'static; + type ElemLen: ArraySize + 'static; /// The type of base field scalars type Scalar: ConstantTimeEq @@ -56,7 +56,18 @@ where + for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>; /// The byte length necessary to represent scalars - type ScalarLen: ArrayLength + 'static; + type ScalarLen: ArraySize + 'static; + + /// Security parameter `k` in bytes (i.e. `k / 8`), as defined in + /// [RFC 9380 ยง8](https://www.rfc-editor.org/rfc/rfc9380#section-8). + /// + /// Used to enforce `H::OutputSize >= 2 * SecurityLevel` in + /// `hash_to_curve` and `hash_to_scalar`, which corresponds to the + /// `expand_message` requirement `len_in_bytes = 2 * k / 8`. + type SecurityLevel: ArraySize; + + /// The OKM length for hash_to_scalar (>= ScalarLen, used by hash_to_field). + type OkmLen: ArraySize + hybrid_array::typenum::NonZero; /// Transforms a password and domain separation tag (DST) into a curve point /// @@ -66,7 +77,9 @@ where fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result where H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual; + H::OutputSize: IsLess + IsLessOrEqual, + Self::SecurityLevel: Mul, + H::OutputSize: IsGreaterOrEqual, Output = True>; /// Hashes a slice of pseudo-random bytes to a scalar /// @@ -76,7 +89,9 @@ where fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual; + H::OutputSize: IsLess + IsLessOrEqual, + Self::SecurityLevel: Mul, + H::OutputSize: IsGreaterOrEqual, Output = True>; /// Get the base point for the group fn base_elem() -> Self::Elem; @@ -90,7 +105,7 @@ where } /// Serializes the `self` group element - fn serialize_elem(elem: Self::Elem) -> GenericArray; + fn serialize_elem(elem: Self::Elem) -> Array; /// Return an element from its fixed-length bytes representation. If the /// element is the identity element, return an error. @@ -104,7 +119,7 @@ where /// /// # Errors /// [`Error::Rng`](crate::Error::Rng) if the random number generator fails. - fn random_scalar(rng: &mut R) -> Result; + fn random_scalar(rng: &mut R) -> Result; /// The multiplicative inverse of this scalar fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar; @@ -117,7 +132,7 @@ where fn zero_scalar() -> Self::Scalar; /// Serializes a scalar to bytes - fn serialize_scalar(scalar: Self::Scalar) -> GenericArray; + fn serialize_scalar(scalar: Self::Scalar) -> Array; /// Return a scalar from its fixed-length bytes representation. If the /// scalar is zero or invalid, then return an error. diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index 6239a87..d5e0b32 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -6,16 +6,20 @@ // of this source tree. You may select, at your option, one of the above-listed // licenses. +use core::num::NonZeroU16; +use core::ops::Mul; use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::Identity; -use digest::core_api::BlockSizeUser; +use digest::block_api::BlockSizeUser; use digest::{FixedOutput, HashMarker}; -use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; -use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; -use generic_array::GenericArray; -use rand_core::{TryCryptoRng, TryRngCore}; +use hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; +use hybrid_array::Array; +use hybrid_array::typenum::{ + IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, True, U2, U16, U32, U64, U256, +}; +use rand_core::{TryCryptoRng, TryRng}; use subtle::ConstantTimeEq; use super::Group; @@ -27,7 +31,7 @@ pub struct Ristretto255; #[cfg(feature = "ristretto255-ciphersuite")] impl crate::CipherSuite for Ristretto255 { - const ID: &'static str = "ristretto255-SHA512"; + const ID: &'static [u8] = b"ristretto255-SHA512"; type Group = Ristretto255; @@ -43,19 +47,31 @@ impl Group for Ristretto255 { type ScalarLen = U32; + type SecurityLevel = U16; + + type OkmLen = U64; + // Implements the `hash_to_ristretto255()` function from // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-B fn hash_to_curve(input: &[&[u8]], dst: &[&[u8]]) -> Result where H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual, + H::OutputSize: IsLess + IsLessOrEqual, + Self::SecurityLevel: Mul, + H::OutputSize: IsGreaterOrEqual, Output = True>, { - let mut uniform_bytes = GenericArray::<_, U64>::default(); - ExpandMsgXmd::::expand_message(input, dst, 64) - .map_err(|_| InternalError::Input)? - .fill_bytes(&mut uniform_bytes); + let mut uniform_bytes = [0u8; 64]; - Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes.into())) + as ExpandMsg>::expand_message( + input, + dst, + NonZeroU16::new(64).unwrap(), + ) + .map_err(|_| InternalError::Input)? + .fill_bytes(&mut uniform_bytes) + .map_err(|_| InternalError::Input)?; + + Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes)) } // Implements the `HashToScalar()` function from @@ -63,14 +79,22 @@ impl Group for Ristretto255 { fn hash_to_scalar(input: &[&[u8]], dst: &[&[u8]]) -> Result where H: BlockSizeUser + Default + FixedOutput + HashMarker, - H::OutputSize: IsLess + IsLessOrEqual, + H::OutputSize: IsLess + IsLessOrEqual, + Self::SecurityLevel: Mul, + H::OutputSize: IsGreaterOrEqual, Output = True>, { - let mut uniform_bytes = GenericArray::<_, U64>::default(); - ExpandMsgXmd::::expand_message(input, dst, 64) - .map_err(|_| InternalError::Input)? - .fill_bytes(&mut uniform_bytes); + let mut uniform_bytes = [0u8; 64]; - Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes.into())) + as ExpandMsg>::expand_message( + input, + dst, + NonZeroU16::new(64).unwrap(), + ) + .map_err(|_| InternalError::Input)? + .fill_bytes(&mut uniform_bytes) + .map_err(|_| InternalError::Input)?; + + Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes)) } fn base_elem() -> Self::Elem { @@ -82,7 +106,7 @@ impl Group for Ristretto255 { } // serialization of a group element - fn serialize_elem(elem: Self::Elem) -> GenericArray { + fn serialize_elem(elem: Self::Elem) -> Array { elem.compress().to_bytes().into() } @@ -94,7 +118,7 @@ impl Group for Ristretto255 { .ok_or(Error::Deserialization) } - fn random_scalar(rng: &mut R) -> Result { + fn random_scalar(rng: &mut R) -> Result { loop { let mut scalar_bytes = [0u8; 32]; rng.try_fill_bytes(&mut scalar_bytes) @@ -119,7 +143,7 @@ impl Group for Ristretto255 { Scalar::ZERO } - fn serialize_scalar(scalar: Self::Scalar) -> GenericArray { + fn serialize_scalar(scalar: Self::Scalar) -> Array { scalar.to_bytes().into() } diff --git a/src/lib.rs b/src/lib.rs index 5915596..3ab7ca1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! We will use the following choice in this example: //! //! ```ignore -//! type CipherSuite = voprf::Ristretto255; +//! type CipherSuite = voprf_vexahub::Ristretto255; //! ``` //! //! ## Modes of Operation @@ -51,14 +51,14 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! use rand::rngs::OsRng; -//! use rand::RngCore; -//! use voprf::OprfServer; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! use rand::rngs::SysRng; +//! use rand::Rng; +//! use voprf_vexahub::OprfServer; //! -//! let mut server_rng = OsRng; +//! let mut server_rng = SysRng; //! let server = OprfServer::::new(&mut server_rng); //! ``` //! @@ -71,14 +71,14 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! use rand::rngs::OsRng; -//! use rand::RngCore; -//! use voprf::OprfClient; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! use rand::rngs::SysRng; +//! use rand::Rng; +//! use voprf_vexahub::OprfClient; //! -//! let mut client_rng = OsRng; +//! let mut client_rng = SysRng; //! let client_blind_result = OprfClient::::blind(b"input", &mut client_rng) //! .expect("Unable to construct client"); //! ``` @@ -92,19 +92,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::OprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::OprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = OprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::OprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::OprfServer; +//! # let mut server_rng = SysRng; //! # let server = OprfServer::::new(&mut server_rng).unwrap(); //! let server_evaluate_result = server.blind_evaluate(&client_blind_result.message); //! ``` @@ -117,19 +117,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::OprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::OprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = OprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::OprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::OprfServer; +//! # let mut server_rng = SysRng; //! # let server = OprfServer::::new(&mut server_rng).unwrap(); //! # let message = server.blind_evaluate(&client_blind_result.message); //! let client_finalize_result = client_blind_result @@ -150,19 +150,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::OprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::OprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = OprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::OprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::OprfServer; +//! # let mut server_rng = SysRng; //! # let server = OprfServer::::new(&mut server_rng).unwrap(); //! # let message = server.blind_evaluate(&client_blind_result.message); //! let client_finalize_result = client_blind_result @@ -197,14 +197,14 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! use rand::rngs::OsRng; -//! use rand::RngCore; -//! use voprf::VoprfServer; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! use rand::rngs::SysRng; +//! use rand::Rng; +//! use voprf_vexahub::VoprfServer; //! -//! let mut server_rng = OsRng; +//! let mut server_rng = SysRng; //! let server = VoprfServer::::new(&mut server_rng).unwrap(); //! //! // To be sent to the client @@ -224,14 +224,14 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! use rand::rngs::OsRng; -//! use rand::RngCore; -//! use voprf::VoprfClient; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! use rand::rngs::SysRng; +//! use rand::Rng; +//! use voprf_vexahub::VoprfClient; //! -//! let mut client_rng = OsRng; +//! let mut client_rng = SysRng; //! let client_blind_result = VoprfClient::::blind(b"input", &mut client_rng) //! .expect("Unable to construct client"); //! ``` @@ -246,19 +246,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::{VoprfServerEvaluateResult, VoprfClient}; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::{VoprfServerEvaluateResult, VoprfClient}; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = VoprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::VoprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! # let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! let VoprfServerEvaluateResult { message, proof } = //! server.blind_evaluate(&mut server_rng, &client_blind_result.message); @@ -273,19 +273,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::VoprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::VoprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = VoprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::VoprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! # let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! # let server_evaluate_result = server.blind_evaluate( //! # &mut server_rng, @@ -314,19 +314,19 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::VoprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::VoprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let client_blind_result = VoprfClient::::blind( //! # b"input", //! # &mut client_rng, //! # ).expect("Unable to construct client"); -//! # use voprf::VoprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! # let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! # let server_evaluate_result = server.blind_evaluate( //! # &mut server_rng, @@ -368,13 +368,13 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::VoprfClient; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::VoprfClient; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! let mut client_rng = OsRng; +//! let mut client_rng = SysRng; //! let mut client_states = vec![]; //! let mut client_messages = vec![]; //! for _ in 0..10 { @@ -392,13 +392,13 @@ //! //! ``` //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::{VoprfServerBatchEvaluateFinishResult, VoprfClient}; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::{VoprfServerBatchEvaluateFinishResult, VoprfClient}; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let mut client_states = vec![]; //! # let mut client_messages = vec![]; //! # for _ in 0..10 { @@ -409,8 +409,8 @@ //! # client_states.push(client_blind_result.state); //! # client_messages.push(client_blind_result.message); //! # } -//! # use voprf::VoprfServer; -//! let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! let prepared_evaluation_elements = server.batch_blind_evaluate_prepare(client_messages.iter()); //! let prepared_elements: Vec<_> = prepared_evaluation_elements.collect(); @@ -426,13 +426,13 @@ //! ``` //! # #[cfg(feature = "alloc")] { //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient}; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::{VoprfServerBatchEvaluateResult, VoprfClient}; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let mut client_states = vec![]; //! # let mut client_messages = vec![]; //! # for _ in 0..10 { @@ -443,8 +443,8 @@ //! # client_states.push(client_blind_result.state); //! # client_messages.push(client_blind_result.message); //! # } -//! # use voprf::VoprfServer; -//! let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! let VoprfServerBatchEvaluateResult { messages, proof } = server //! .batch_blind_evaluate(&mut server_rng, &client_messages) @@ -460,13 +460,13 @@ //! ``` //! # #[cfg(feature = "alloc")] { //! # #[cfg(feature = "ristretto255")] -//! # type CipherSuite = voprf::Ristretto255; +//! # type CipherSuite = voprf_vexahub::Ristretto255; //! # #[cfg(not(feature = "ristretto255"))] -//! # type CipherSuite = p256::NistP256; -//! # use voprf::{VoprfServerBatchEvaluateResult, VoprfClient}; -//! # use rand::{rngs::OsRng, RngCore}; +//! # type CipherSuite = voprf_vexahub::P256CipherSuite; +//! # use voprf_vexahub::{VoprfServerBatchEvaluateResult, VoprfClient}; +//! # use rand::{rngs::SysRng, Rng}; //! # -//! # let mut client_rng = OsRng; +//! # let mut client_rng = SysRng; //! # let mut client_states = vec![]; //! # let mut client_messages = vec![]; //! # for _ in 0..10 { @@ -477,8 +477,8 @@ //! # client_states.push(client_blind_result.state); //! # client_messages.push(client_blind_result.message); //! # } -//! # use voprf::VoprfServer; -//! # let mut server_rng = OsRng; +//! # use voprf_vexahub::VoprfServer; +//! # let mut server_rng = SysRng; //! # let server = VoprfServer::::new(&mut server_rng).unwrap(); //! # let VoprfServerBatchEvaluateResult { messages, proof } = server //! # .batch_blind_evaluate(&mut server_rng, &client_messages) @@ -566,6 +566,18 @@ mod voprf; #[cfg(test)] mod tests; +#[cfg(feature = "doctest")] +#[derive(Debug)] +#[doc(hidden)] +pub struct P256CipherSuite; + +#[cfg(feature = "doctest")] +impl CipherSuite for P256CipherSuite { + const ID: &'static [u8] = b"P256-SHA256"; + type Group = p256::NistP256; + type Hash = sha2::Sha256; +} + // Exports pub use crate::ciphersuite::CipherSuite; diff --git a/src/oprf.rs b/src/oprf.rs index d1573a5..488c93b 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -12,13 +12,13 @@ use core::iter::{self, Map}; use derive_where::derive_where; use digest::{Digest, Output}; -use generic_array::typenum::Unsigned; -use generic_array::GenericArray; -use rand_core::{TryCryptoRng, TryRngCore}; +use hybrid_array::Array; +use hybrid_array::typenum::Unsigned; +use rand_core::{TryCryptoRng, TryRng}; use crate::common::{ - derive_key_internal, deterministic_blind_unchecked, hash_to_group, i2osp_2, - server_evaluate_hash_input, BlindedElement, EvaluationElement, Mode, STR_FINALIZE, + BlindedElement, EvaluationElement, Mode, STR_FINALIZE, derive_key_internal, + deterministic_blind_unchecked, hash_to_group, i2osp_2, server_evaluate_hash_input, }; #[cfg(feature = "serde")] use crate::serialization::serde::Scalar; @@ -73,7 +73,7 @@ impl OprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { @@ -146,8 +146,8 @@ impl OprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { - let mut seed = GenericArray::<_, ::ScalarLen>::default(); + pub fn new(rng: &mut R) -> Result { + let mut seed = Array::<_, ::ScalarLen>::default(); rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; Self::new_from_seed(&seed, &[]) } @@ -267,12 +267,12 @@ fn finalize_after_unblind< mod tests { use core::ptr; - use rand::rngs::OsRng; - use rand::TryRngCore; + use rand::TryRng; + use rand::rngs::SysRng; use super::*; - use crate::common::{Dst, STR_HASH_TO_GROUP}; use crate::Group; + use crate::common::{Dst, STR_HASH_TO_GROUP}; fn prf( input: &[u8], @@ -280,7 +280,7 @@ mod tests { info: &[u8], mode: Mode, ) -> Output { - let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + 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; @@ -293,7 +293,7 @@ mod tests { fn base_retrieval() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); let server = OprfServer::::new(&mut rng).unwrap(); let message = server.blind_evaluate(&client_blind_result.message); @@ -303,7 +303,7 @@ mod tests { } fn base_inversion_unsalted() { - let mut rng = OsRng; + let mut rng = SysRng; let mut input = [0u8; 64]; rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = OprfClient::::blind(&input, &mut rng).unwrap(); @@ -312,7 +312,7 @@ mod tests { .finalize(&input, &EvaluationElement(client_blind_result.message.0)) .unwrap(); - let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); + 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() @@ -324,7 +324,7 @@ mod tests { fn server_evaluate() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); let server = OprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&client_blind_result.message); @@ -348,7 +348,7 @@ mod tests { fn zeroize_oprf_client() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); let mut state = client_blind_result.state; @@ -362,7 +362,7 @@ mod tests { fn zeroize_oprf_server() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = OprfClient::::blind(input, &mut rng).unwrap(); let server = OprfServer::::new(&mut rng).unwrap(); let mut message = server.blind_evaluate(&client_blind_result.message); diff --git a/src/poprf.rs b/src/poprf.rs index 7351408..7f12169 100644 --- a/src/poprf.rs +++ b/src/poprf.rs @@ -14,14 +14,14 @@ use core::iter::{self, Map, Repeat, Zip}; use derive_where::derive_where; use digest::{Digest, Output, OutputSizeUser}; -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; -use rand_core::{TryCryptoRng, TryRngCore}; +use hybrid_array::typenum::Unsigned; +use hybrid_array::{Array, ArraySize}; +use rand_core::{TryCryptoRng, TryRng}; use crate::common::{ - derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, - server_evaluate_hash_input, verify_proof, BlindedElement, Dst, EvaluationElement, Mode, - PreparedEvaluationElement, Proof, STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO, + BlindedElement, Dst, 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, }; #[cfg(feature = "serde")] use crate::serialization::serde::{Element, Scalar}; @@ -75,7 +75,7 @@ impl PoprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { @@ -134,7 +134,7 @@ impl PoprfClient { info: Option<&[u8]>, ) -> Result> where - <::Hash as OutputSizeUser>::OutputSize: ArrayLength, + <::Hash as OutputSizeUser>::OutputSize: ArraySize, { let clients = core::array::from_ref(self); let messages = core::array::from_ref(evaluation_element); @@ -170,7 +170,7 @@ impl PoprfClient { <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, &'a IM: 'a + IntoIterator>, <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, - <::Hash as OutputSizeUser>::OutputSize: ArrayLength, + <::Hash as OutputSizeUser>::OutputSize: ArraySize, { let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?; @@ -189,8 +189,8 @@ impl PoprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { - let mut seed = GenericArray::<_, ::ScalarLen>::default(); + pub fn new(rng: &mut R) -> Result { + let mut seed = Array::<_, ::ScalarLen>::default(); rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; Self::new_from_seed(&seed, &[]) @@ -235,7 +235,7 @@ impl PoprfServer { /// # Errors /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn blind_evaluate( + pub fn blind_evaluate( &self, rng: &mut R, blinded_element: &BlindedElement, @@ -273,7 +273,7 @@ impl PoprfServer { /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. #[cfg(feature = "alloc")] - pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, IE>( + pub fn batch_blind_evaluate<'a, R: TryRng + TryCryptoRng, IE>( &self, rng: &mut R, blinded_elements: &'a IE, @@ -346,7 +346,7 @@ impl PoprfServer { pub fn batch_blind_evaluate_finish< 'a, 'b, - R: TryRngCore + TryCryptoRng, + R: TryRng + TryCryptoRng, IB: Iterator> + ExactSizeIterator, IE, >( @@ -371,7 +371,7 @@ impl PoprfServer { tweaked_key, prepared_evaluation_elements .into_iter() - .map(|element| element.0 .0), + .map(|element| element.0.0), blinded_elements.map(|element| element.0), Mode::Poprf, )?; @@ -379,7 +379,7 @@ impl PoprfServer { let messages = prepared_evaluation_elements.into_iter().map(, ) -> _>::from( - |element| EvaluationElement(element.0 .0), + |element| EvaluationElement(element.0.0), )); Ok(PoprfServerBatchEvaluateFinishResult { messages, proof }) @@ -569,7 +569,7 @@ fn compute_tweaked_key( let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let framed_info = [STR_INFO.as_slice(), &info_len, info]; - let dst = Dst::new::(STR_HASH_TO_SCALAR, 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.as_dst()).unwrap(); @@ -602,7 +602,7 @@ fn compute_tweak( let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?; let framed_info = [STR_INFO.as_slice(), &info_len, info]; - let dst = Dst::new::(STR_HASH_TO_SCALAR, 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.as_dst()).unwrap(); @@ -692,7 +692,7 @@ fn finalize_after_unblind< info: Option<&'a [u8]>, ) -> Result> where - <::Hash as OutputSizeUser>::OutputSize: ArrayLength, + <::Hash as OutputSizeUser>::OutputSize: ArraySize, { if unblinded_elements.len() != inputs.len() { return Err(Error::Batch); @@ -733,11 +733,11 @@ where mod tests { use core::ptr; - use rand::rngs::OsRng; + use rand::rngs::SysRng; use super::*; - use crate::common::STR_HASH_TO_GROUP; use crate::Group; + use crate::common::STR_HASH_TO_GROUP; fn prf( input: &[u8], @@ -747,7 +747,7 @@ mod tests { ) -> Output { let t = compute_tweak::(key, Some(info)).unwrap(); - let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + 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 @@ -763,7 +763,7 @@ mod tests { fn verifiable_retrieval() { let input = b"input"; let info = b"info"; - let mut rng = OsRng; + let mut rng = SysRng; let server = PoprfServer::::new(&mut rng).unwrap(); let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); let server_result = server @@ -786,14 +786,14 @@ mod tests { fn verifiable_bad_public_key() { let input = b"input"; let info = b"info"; - let mut rng = OsRng; + let mut rng = SysRng; let server = PoprfServer::::new(&mut rng).unwrap(); let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); let server_result = server .blind_evaluate(&mut rng, &client_blind_result.message, Some(info)) .unwrap(); let wrong_pk = { - let dst = Dst::new::(STR_HASH_TO_GROUP, 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.as_dst()).unwrap() }; @@ -810,7 +810,7 @@ mod tests { fn verifiable_server_evaluate() { let input = b"input"; let info = Some(b"info".as_slice()); - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); let server = PoprfServer::::new(&mut rng).unwrap(); let server_result = server @@ -842,7 +842,7 @@ mod tests { fn zeroize_verifiable_client() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); let mut state = client_blind_result.state; @@ -857,7 +857,7 @@ mod tests { fn zeroize_verifiable_server() { let input = b"input"; let info = b"info"; - let mut rng = OsRng; + let mut rng = SysRng; let server = PoprfServer::::new(&mut rng).unwrap(); let client_blind_result = PoprfClient::::blind(input, &mut rng).unwrap(); let server_result = server diff --git a/src/serialization.rs b/src/serialization.rs index bafdcf5..ecaa8d6 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -9,9 +9,8 @@ //! Handles the serialization of each of the components used in the VOPRF //! protocol -use generic_array::sequence::Concat; -use generic_array::typenum::{Sum, Unsigned}; -use generic_array::GenericArray; +use hybrid_array::Array; +use hybrid_array::typenum::{Sum, Unsigned}; use crate::{ BlindedElement, CipherSuite, Error, EvaluationElement, Group, OprfClient, OprfServer, @@ -28,7 +27,7 @@ pub type OprfClientLen = <::Group as Group>::ScalarLen; impl OprfClient { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_scalar(self.blind) } @@ -51,7 +50,7 @@ pub type VoprfClientLen = Sum< impl VoprfClient { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { ::serialize_scalar(self.blind) .concat(::serialize_elem(self.blinded_element)) } @@ -79,7 +78,7 @@ pub type PoprfClientLen = Sum< impl PoprfClient { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { ::serialize_scalar(self.blind) .concat(::serialize_elem(self.blinded_element)) } @@ -104,7 +103,7 @@ pub type OprfServerLen = <::Group as Group>::ScalarLen; impl OprfServer { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_scalar(self.sk) } @@ -127,7 +126,7 @@ pub type VoprfServerLen = Sum< impl VoprfServer { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk)) } @@ -151,7 +150,7 @@ pub type PoprfServerLen = Sum< impl PoprfServer { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_scalar(self.sk).concat(CS::Group::serialize_elem(self.pk)) } @@ -175,7 +174,7 @@ pub type ProofLen = Sum< impl Proof { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_scalar(self.c_scalar) .concat(CS::Group::serialize_scalar(self.s_scalar)) } @@ -197,7 +196,7 @@ pub type BlindedElementLen = <::Group as Group>::ElemLen; impl BlindedElement { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_elem(self.0) } @@ -217,7 +216,7 @@ pub type EvaluationElementLen = <::Group as Group>::ElemL impl EvaluationElement { /// Serialization into bytes - pub fn serialize(&self) -> GenericArray> { + pub fn serialize(&self) -> Array> { CS::Group::serialize_elem(self.0) } @@ -266,7 +265,7 @@ impl SliceExt for [T] { pub(crate) mod serde { use core::marker::PhantomData; - use generic_array::GenericArray; + use hybrid_array::Array; use serde::de::{Deserializer, Error}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; @@ -280,7 +279,7 @@ pub(crate) mod serde { where D: Deserializer<'de>, { - GenericArray::<_, G::ElemLen>::deserialize(deserializer) + Array::<_, G::ElemLen>::deserialize(deserializer) .and_then(|bytes| G::deserialize_elem(&bytes).map_err(D::Error::custom)) } @@ -299,7 +298,7 @@ pub(crate) mod serde { where D: Deserializer<'de>, { - GenericArray::<_, G::ScalarLen>::deserialize(deserializer) + Array::<_, G::ScalarLen>::deserialize(deserializer) .and_then(|bytes| G::deserialize_scalar(&bytes).map_err(D::Error::custom)) } diff --git a/src/tests/mock_rng.rs b/src/tests/mock_rng.rs index 3268d41..af4d695 100644 --- a/src/tests/mock_rng.rs +++ b/src/tests/mock_rng.rs @@ -9,7 +9,8 @@ use alloc::vec::Vec; use core::cmp::min; -use rand_core::{CryptoRng, RngCore}; +use core::convert::Infallible; +use rand_core::{TryCryptoRng, TryRng}; /// A simple implementation of `RngCore` for testing purposes. /// @@ -38,23 +39,35 @@ fn rotate_left(data: &mut [T], steps: usize) { data.reverse(); } -impl RngCore for CycleRng { - fn next_u32(&mut self) -> u32 { - unimplemented!() +impl TryRng for CycleRng { + type Error = Infallible; + + fn try_next_u32(&mut self) -> Result { + let mut buf = [0u8; 4]; + + self.try_fill_bytes(&mut buf)?; + + Ok(u32::from_le_bytes(buf)) } - #[inline] - fn next_u64(&mut self) -> u64 { - unimplemented!() + fn try_next_u64(&mut self) -> Result { + let mut buf = [0u8; 8]; + + self.try_fill_bytes(&mut buf)?; + + Ok(u64::from_le_bytes(buf)) } - #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { let len = min(self.v.len(), dest.len()); + dest[..len].copy_from_slice(&self.v[..len]); + rotate_left(&mut self.v, len); + + Ok(()) } } // This is meant for testing only -impl CryptoRng for CycleRng {} +impl TryCryptoRng for CycleRng {} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 71e7089..2208070 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -10,3 +10,21 @@ mod cfrg_vectors; mod mock_rng; mod parser; mod test_cfrg_vectors; + +impl crate::CipherSuite for p256::NistP256 { + const ID: &'static [u8] = ::ID; + type Group = p256::NistP256; + type Hash = sha2::Sha256; +} + +impl crate::CipherSuite for p384::NistP384 { + const ID: &'static [u8] = ::ID; + type Group = p384::NistP384; + type Hash = sha2::Sha384; +} + +impl crate::CipherSuite for p521::NistP521 { + const ID: &'static [u8] = ::ID; + type Group = p521::NistP521; + type Hash = sha2::Sha512; +} diff --git a/src/tests/parser.rs b/src/tests/parser.rs index b2d4b99..5d41829 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -15,55 +15,50 @@ pub(crate) fn rfc_to_json(input: &str) -> String { } fn parse_ciphersuites(input: &str) -> String { - let re = regex::Regex::new(r"\nA\.\d\. (?P.+?)\n\n").unwrap(); + let re = regex::Regex::new(r"\nA\.\d\. {2}(?P.+?)\n\n").unwrap(); let mut ciphersuites = vec![]; let chunks: Vec<&str> = re.split(input).collect(); - let mut count = 1; - for caps in re.captures_iter(input) { + for (count, caps) in (1..).zip(re.captures_iter(input)) { let ciphersuite = format!( "\"{}\": {{ {} }}", &caps["ciphersuite"], parse_modes(chunks[count]) ); + ciphersuites.push(ciphersuite); - count += 1; } ciphersuites.join(",\n") } fn parse_modes(input: &str) -> String { - let re = regex::Regex::new(r"A\.\d.\d\. (?P.*?) Mode").unwrap(); + let re = regex::Regex::new(r"A\.\d.\d\. {2}(?P.*?) Mode").unwrap(); let mut modes = vec![]; let chunks: Vec<&str> = re.split(input).collect(); - let mut count = 1; - for caps in re.captures_iter(input) { + for (count, caps) in (1..).zip(re.captures_iter(input)) { let mode = format!( "\"{}\": [\n {} \n]", &caps["mode"], parse_vectors(chunks[count]) ); modes.push(mode); - count += 1; } modes.join(",\n") } fn parse_vectors(input: &str) -> String { - let re = regex::Regex::new(r"A\.\d.\d\.\d\. Test Vector.*+\n").unwrap(); + let re = regex::Regex::new(r"A\.\d.\d\.\d\. {2}Test Vector.*+\n").unwrap(); let mut vectors = vec![]; let chunks: Vec<&str> = re.split(input).collect(); let init_params = parse_params(chunks[0]); - let mut count = 1; - for _ in re.captures_iter(input) { + for (count, _) in (1..).zip(re.captures_iter(input)) { let params = format!("{{\n{},\n{}\n}}", init_params, parse_params(chunks[count])); vectors.push(params); - count += 1; } vectors.join(",\n") diff --git a/src/tests/test_cfrg_vectors.rs b/src/tests/test_cfrg_vectors.rs index 71ef4d7..99c96aa 100644 --- a/src/tests/test_cfrg_vectors.rs +++ b/src/tests/test_cfrg_vectors.rs @@ -360,7 +360,7 @@ fn test_voprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) assert_eq!(¶meter, &message.serialize().as_slice()); } - assert_eq!(¶meters.proof, &proof.serialize().as_slice()); + assert_eq!(¶meters.proof, &proof.serialize().to_vec()); } Ok(()) } @@ -386,8 +386,7 @@ fn test_poprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) blinded_elements.iter(), &prepared_evaluation_elements, &prepared_tweak, - ) - .unwrap(); + )?; let messages: Vec<_> = messages.collect(); @@ -395,7 +394,7 @@ fn test_poprf_blind_evaluate(tvs: &[VOPRFTestVectorParameters]) assert_eq!(¶meter, &message.serialize().as_slice()); } - assert_eq!(¶meters.proof, &proof.serialize().as_slice()); + assert_eq!(¶meters.proof, &proof.serialize().to_vec()); } Ok(()) } diff --git a/src/voprf.rs b/src/voprf.rs index e4c9807..ebf43fd 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -14,14 +14,14 @@ use core::iter::{self, Map, Repeat, Zip}; use derive_where::derive_where; use digest::{Digest, Output}; -use generic_array::typenum::Unsigned; -use generic_array::GenericArray; -use rand_core::{TryCryptoRng, TryRngCore}; +use hybrid_array::Array; +use hybrid_array::typenum::Unsigned; +use rand_core::{TryCryptoRng, TryRng}; use crate::common::{ + BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, 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, + server_evaluate_hash_input, verify_proof, }; #[cfg(feature = "serde")] use crate::serialization::serde::{Element, Scalar}; @@ -75,7 +75,7 @@ impl VoprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { @@ -196,8 +196,8 @@ impl VoprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { - let mut seed = GenericArray::<_, ::ScalarLen>::default(); + pub fn new(rng: &mut R) -> Result { + let mut seed = Array::<_, ::ScalarLen>::default(); rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; // This can't fail as the hash output is type constrained. Self::new_from_seed(&seed, &[]) @@ -238,7 +238,7 @@ impl VoprfServer { /// Computes the second step for the multiplicative blinding version of /// DH-OPRF. This message is sent from the server (who holds the OPRF key) /// to the client. - pub fn blind_evaluate( + pub fn blind_evaluate( &self, rng: &mut R, blinded_element: &BlindedElement, @@ -271,7 +271,7 @@ impl VoprfServer { /// [`Error::Batch`] if the number of `blinded_elements` and /// `evaluation_elements` don't match or is longer then [`u16::MAX`] #[cfg(feature = "alloc")] - pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, I>( + pub fn batch_blind_evaluate<'a, R: TryRng + TryCryptoRng, I>( &self, rng: &mut R, blinded_elements: &'a I, @@ -322,7 +322,7 @@ impl VoprfServer { pub fn batch_blind_evaluate_finish< 'a, 'b, - R: TryRngCore + TryCryptoRng, + R: TryRng + TryCryptoRng, IB: Iterator> + ExactSizeIterator, IE, >( @@ -343,14 +343,14 @@ impl VoprfServer { g, self.pk, blinded_elements.map(|element| element.0), - evaluation_elements.into_iter().map(|element| element.0 .0), + evaluation_elements.into_iter().map(|element| element.0.0), Mode::Voprf, )?; let messages = evaluation_elements.into_iter().map(, ) -> EvaluationElement>::from( - |element| EvaluationElement(element.0 .0), + |element| EvaluationElement(element.0.0), )); Ok(VoprfServerBatchEvaluateFinishResult { messages, proof }) @@ -550,18 +550,18 @@ mod tests { use ::alloc::vec; use ::alloc::vec::Vec; - use rand::rngs::OsRng; + use rand::rngs::SysRng; use super::*; - use crate::common::{Dst, STR_HASH_TO_GROUP}; use crate::Group; + use crate::common::{Dst, STR_HASH_TO_GROUP}; fn prf( input: &[u8], key: ::Scalar, mode: Mode, ) -> Output { - let dst = Dst::new::(STR_HASH_TO_GROUP, mode); + 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; @@ -574,7 +574,7 @@ mod tests { fn verifiable_retrieval() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); @@ -592,7 +592,7 @@ mod tests { } fn verifiable_batch_retrieval() { - let mut rng = OsRng; + let mut rng = SysRng; let mut inputs = vec![]; let mut client_states = vec![]; let mut client_messages = vec![]; @@ -636,7 +636,7 @@ mod tests { } fn verifiable_batch_bad_public_key() { - let mut rng = OsRng; + let mut rng = SysRng; let mut inputs = vec![]; let mut client_states = vec![]; let mut client_messages = vec![]; @@ -662,7 +662,7 @@ mod tests { .unwrap(); let messages: Vec<_> = messages.collect(); let wrong_pk = { - let dst = Dst::new::(STR_HASH_TO_GROUP, 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.as_dst()).unwrap() }; @@ -673,12 +673,12 @@ mod tests { fn verifiable_bad_public_key() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let wrong_pk = { - let dst = Dst::new::(STR_HASH_TO_GROUP, 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.as_dst()).unwrap() }; @@ -693,7 +693,7 @@ mod tests { fn verifiable_server_evaluate() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); @@ -722,7 +722,7 @@ mod tests { fn zeroize_voprf_client() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let mut state = client_blind_result.state; @@ -736,7 +736,7 @@ mod tests { fn zeroize_voprf_server() { let input = b"input"; - let mut rng = OsRng; + let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message);