Skip to content

Add decaf448 support #139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ jobs:
matrix:
backend_feature:
- --features ristretto255-ciphersuite
- --features decaf448-ciphersuite
- --features ristretto255-ciphersuite,decaf448-ciphersuite
-
frontend_feature:
-
- --features danger
- --features serde
toolchain:
- stable
- 1.83.0
- 1.85.0
name: test
steps:
- name: Checkout sources
Expand Down Expand Up @@ -94,6 +96,8 @@ jobs:
backend_feature:
-
- --features ristretto255-ciphersuite
- --features decaf448-ciphersuite
- --features ristretto255-ciphersuite,decaf448-ciphersuite
frontend_feature:
-
- --features danger
Expand Down
48 changes: 33 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,86 @@
authors = ["Kevin Lewi <[email protected]>"]
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"
readme = "README.md"
repository = "https://github.com/facebook/voprf/"
rust-version = "1.83"
rust-version = "1.85"
version = "0.5.0"

[features]
alloc = []
danger = []
decaf448 = ["dep:ed448"]
decaf448-ciphersuite = ["decaf448", "dep:sha3"]
default = ["ristretto255-ciphersuite", "dep:serde"]
ristretto255 = ["dep:curve25519-dalek", "generic-array/more_lengths"]
ristretto255 = ["dep:curve25519-dalek"]
ristretto255-ciphersuite = ["ristretto255", "dep:sha2"]
serde = ["generic-array/serde", "dep:serde"]
serde = ["hybrid-array/serde", "dep:serde"]
std = ["alloc"]

[dependencies]
curve25519-dalek = { version = "4", default-features = false, features = [
"group",
"rand_core",
"zeroize",
], optional = true }
derive-where = { version = "1", features = ["zeroize-on-drop"] }
digest = "0.10"
digest = "0.11.0-pre.10"
displaydoc = { version = "0.2", default-features = false }
elliptic-curve = { version = "0.13", features = [
ed448 = { version = "0.17.0-pre.0", default-features = false, features = [
"zeroize",
], optional = true }
elliptic-curve = { version = "0.14.0-rc.1", features = [
"hash2curve",
"sec1",
"voprf",
] }
generic-array = "0.14"
rand_core = { version = "0.6", default-features = false }
hybrid-array = "0.3"
rand_core = { version = "0.9", default-features = false }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true }
sha2 = { version = "0.11.0-pre.5", default-features = false, optional = true }
sha3 = { version = "0.11.0-pre.5", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1.5", default-features = false }

[dev-dependencies]
generic-array = { version = "0.14", features = ["more_lengths"] }
hex = "0.4"
p256 = { version = "0.13", default-features = false, features = [
p256 = { version = "0.14.0-pre.2", default-features = false, features = [
"hash2curve",
"voprf",
] }
p384 = { version = "0.13", default-features = false, features = [
p384 = { version = "0.14.0-pre.2", default-features = false, features = [
"hash2curve",
"voprf",
] }
p521 = { version = "0.13.3", default-features = false, features = [
p521 = { version = "0.14.0-pre.2", default-features = false, features = [
"hash2curve",
"voprf",
] }
proptest = "1"
rand = "0.8"
rand = "0.9"
regex = "1"
serde_json = "1"
sha2 = "0.10"
sha2 = "0.11.0-pre.5"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = []

[patch.crates-io]
crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git", rev = "e97beae6593c30b4477b7f29e30e5372cd8204bf" }
curve25519-dalek = { git = "https://github.com/khonsulabs/curve25519-dalek", branch = "voprf" }
ed448 = { git = "https://github.com/khonsulabs/elliptic-curves", branch = "ed448-voprf" }
elliptic-curve = { git = "https://github.com/RustCrypto/traits", rev = "204a4e030fa98863429ccd3797e12f9e7c45dc33" }
ff = { git = "https://github.com/zkcrypto/ff", branch = "release-0.14.0" }
group = { git = "https://github.com/pinkforest/group", branch = "bump-rand-0.9" }
p256 = { git = "https://github.com/khonsulabs/elliptic-curves", branch = "ed448-voprf" }
p384 = { git = "https://github.com/khonsulabs/elliptic-curves", branch = "ed448-voprf" }
p521 = { git = "https://github.com/khonsulabs/elliptic-curves", branch = "ed448-voprf" }
proptest = { git = "https://github.com/daxpedda/proptest", branch = "rand-0.9" }
16 changes: 11 additions & 5 deletions src/ciphersuite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
use digest::core_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker, OutputSizeUser};
use elliptic_curve::VoprfParameters;
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd};
use hybrid_array::typenum::{IsLess, IsLessOrEqual, U256, U65536};

use crate::Group;

/// Configures the underlying primitives used in VOPRF
pub trait CipherSuite
where
<Self::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<Self::Hash as BlockSizeUser>::BlockSize>,
<Self::Hash as OutputSizeUser>::OutputSize: IsLess<U65536>,
{
/// The ciphersuite identifier as dictated by
/// <https://www.rfc-editor.org/rfc/rfc9497>
Expand All @@ -31,19 +31,25 @@ where

/// The main hash function to use (for HKDF computations and hashing
/// transcripts).
type Hash: BlockSizeUser + Default + FixedOutput + HashMarker;
type Hash: Default + FixedOutput + HashMarker;

/// Which function to use for `expand_message` in `HashToGroup()` and
/// `HashToScalar()`.
type ExpandMsg: for<'a> ExpandMsg<'a>;
}

impl<T: VoprfParameters> CipherSuite for T
where
T: Group,
T::Hash: BlockSizeUser + Default + FixedOutput + HashMarker,
<T::Hash as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
IsLess<U256> + IsLess<U65536> + IsLessOrEqual<<T::Hash as BlockSizeUser>::BlockSize>,
{
const ID: &'static str = T::ID;

type Group = T;

type Hash = T::Hash;

type ExpandMsg = ExpandMsgXmd<Self::Hash>;
}
71 changes: 33 additions & 38 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use core::ops::Add;

use derive_where::derive_where;
use digest::{Digest, Output};
use generic_array::sequence::Concat;
use generic_array::typenum::{IsLess, Unsigned, U2, U256, U9};
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use hybrid_array::typenum::{IsLess, U9, U65536, Unsigned};
use hybrid_array::{Array, ArrayN, ArraySize};
use rand_core::{TryCryptoRng, TryRngCore};
use subtle::ConstantTimeEq;

#[cfg(feature = "serde")]
Expand All @@ -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<u8, 5> = Array(*b"Seed-");
pub(crate) const STR_DERIVE_KEYPAIR: ArrayN<u8, 13> = Array(*b"DeriveKeyPair");
pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite";
pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge";
pub(crate) const STR_INFO: [u8; 4] = *b"Info";
pub(crate) const STR_OPRF: [u8; 7] = *b"OPRFV1-";
pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-";
pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-";
pub(crate) const STR_HASH_TO_SCALAR: ArrayN<u8, 13> = Array(*b"HashToScalar-");
pub(crate) const STR_HASH_TO_GROUP: ArrayN<u8, 12> = Array(*b"HashToGroup-");

/// Determines the mode of operation (either base mode or verifiable mode). This
/// is only used for custom implementations for [`Group`].
Expand Down Expand Up @@ -128,20 +127,20 @@ pub struct Proof<CS: CipherSuite> {

/// Can only fail with [`Error::Batch`].
#[allow(clippy::many_single_char_names)]
pub(crate) fn generate_proof<CS: CipherSuite, R: RngCore + CryptoRng>(
pub(crate) fn generate_proof<CS: CipherSuite, R: TryRngCore + TryCryptoRng>(
rng: &mut R,
k: <CS::Group as Group>::Scalar,
a: <CS::Group as Group>::Elem,
b: <CS::Group as Group>::Elem,
cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
mode: Mode,
) -> Result<Proof<CS>> {
) -> Result<Proof<CS>, Error<R::Error>> {
// https://www.rfc-editor.org/rfc/rfc9497#section-2.2.1

let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode).map_err(Error::cast)?;

let r = CS::Group::random_scalar(rng);
let r = CS::Group::random_scalar(rng).map_err(Error::Random)?;
let t2 = a * &r;
let t3 = m * &r;

Expand Down Expand Up @@ -178,9 +177,9 @@ pub(crate) fn generate_proof<CS: CipherSuite, R: RngCore + CryptoRng>(
&STR_CHALLENGE,
];

let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
let c_scalar = CS::Group::hash_to_scalar::<CS::ExpandMsg>(&h2_input, &dst.as_dst()).unwrap();
let s_scalar = r - &(c_scalar * &k);

Ok(Proof { c_scalar, s_scalar })
Expand Down Expand Up @@ -234,9 +233,9 @@ pub(crate) fn verify_proof<CS: CipherSuite>(
&STR_CHALLENGE,
];

let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
let c = CS::Group::hash_to_scalar::<CS::ExpandMsg>(&h2_input, &dst.as_dst()).unwrap();

match c.ct_eq(&proof.c_scalar).into() {
true => Ok(()),
Expand Down Expand Up @@ -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::<CS, _, _>(STR_SEED, mode);
let seed_dst = Dst::new::<CS, _>(STR_SEED, mode);

// h1Input = I2OSP(len(Bm), 2) || Bm ||
// I2OSP(len(seedDST), 2) || seedDST
Expand Down Expand Up @@ -308,9 +307,9 @@ fn compute_composites<
&STR_COMPOSITE,
];

let dst = Dst::new::<CS, _, _>(STR_HASH_TO_SCALAR, mode);
let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
// This can't fail, the size of the `input` is known.
let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
let di = CS::Group::hash_to_scalar::<CS::ExpandMsg>(&h2_input, &dst.as_dst()).unwrap();
m = c * &di + &m;
z = match k_option {
Some(_) => z,
Expand All @@ -337,15 +336,15 @@ pub(crate) fn derive_key_internal<CS: CipherSuite>(
info: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Scalar, Error> {
let dst = Dst::new::<CS, _, _>(STR_DERIVE_KEYPAIR, mode);
let dst = Dst::new::<CS, _>(STR_DERIVE_KEYPAIR, mode);

let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;

for counter in 0_u8..=u8::MAX {
// deriveInput = seed || I2OSP(len(info), 2) || info
// skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair"
// || contextString)
let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
let sk_s = CS::Group::hash_to_scalar::<CS::ExpandMsg>(
&[seed, &info_len, info, &counter.to_be_bytes()],
&dst.as_dst(),
)
Expand Down Expand Up @@ -410,16 +409,16 @@ pub(crate) fn hash_to_group<CS: CipherSuite>(
input: &[u8],
mode: Mode,
) -> Result<<CS::Group as Group>::Elem> {
let dst = Dst::new::<CS, _, _>(STR_HASH_TO_GROUP, mode);
CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
let dst = Dst::new::<CS, _>(STR_HASH_TO_GROUP, mode);
CS::Group::hash_to_curve::<CS::ExpandMsg>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
}

/// Internal function that finalizes the hash input for OPRF, VOPRF & POPRF.
/// Returned values can only fail with [`Error::Input`].
pub(crate) fn server_evaluate_hash_input<CS: CipherSuite>(
input: &[u8],
info: Option<&[u8]>,
issued_element: GenericArray<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
issued_element: Array<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
) -> Result<Output<CS::Hash>> {
// OPRF & VOPRF
// hashInput = I2OSP(len(input), 2) || input ||
Expand All @@ -442,30 +441,28 @@ pub(crate) fn server_evaluate_hash_input<CS: CipherSuite>(
.chain_update(info.as_ref());
}
Ok(hash
.chain_update(i2osp_2(issued_element.as_ref().len()).map_err(|_| Error::Input)?)
.chain_update(i2osp_2_array(&issued_element))
.chain_update(issued_element)
.chain_update(STR_FINALIZE)
.finalize())
}

pub(crate) struct Dst<L: ArrayLength<u8>> {
dst_1: GenericArray<u8, L>,
pub(crate) struct Dst<L: ArraySize> {
dst_1: Array<u8, L>,
dst_2: &'static str,
}

impl<L: ArrayLength<u8>> Dst<L> {
pub(crate) fn new<CS, T, TL>(par_1: T, mode: Mode) -> Self
impl<L: ArraySize> Dst<L> {
pub(crate) fn new<CS, TL>(par_1: Array<u8, TL>, mode: Mode) -> Self
where
CS: CipherSuite,
T: Into<GenericArray<u8, TL>>,
TL: ArrayLength<u8> + Add<U9, Output = L>,
TL: ArraySize + Add<U9, Output = L>,
{
let par_1 = par_1.into();
// Generates the contextString parameter as defined in
// <https://www.rfc-editor.org/rfc/rfc9497#section-3.1>
let par_2 = GenericArray::from(STR_OPRF)
.concat([mode.to_u8()].into())
.concat([b'-'].into());
let par_2 = ArrayN::<u8, 7>::from(STR_OPRF)
.concat(ArrayN::<u8, 1>::from([mode.to_u8()]))
.concat(ArrayN::<u8, 1>::from([b'-']));

let dst_1 = par_1.concat(par_2);
let dst_2 = CS::ID;
Expand Down Expand Up @@ -518,8 +515,6 @@ pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
.map_err(|_| InternalError::I2osp)
}

pub(crate) fn i2osp_2_array<L: ArrayLength<u8> + IsLess<U256>>(
_: &GenericArray<u8, L>,
) -> GenericArray<u8, U2> {
pub(crate) fn i2osp_2_array<L: ArraySize + IsLess<U65536>>(_: &Array<u8, L>) -> ArrayN<u8, 2> {
L::U16.to_be_bytes().into()
}
Loading
Loading