chore: bump to v1.0.0-rc.1, reject trailing bytes, reject identity elemnts, more tests (#10)
Rust CI / cargo fmt (push) Successful in 3s
Rust CI / cargo clippy (push) Successful in 26s
Rust CI / test (1.87.0 / no backend / no frontend) (push) Successful in 1m27s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 1m20s
Rust CI / test (1.87.0 / no backend / --features danger) (push) Successful in 1m30s
Rust CI / test (stable / no backend / --features danger) (push) Successful in 1m21s
Publish / publish (release) Successful in 19s
Rust CI / test (1.87.0 / no backend / --features serde) (push) Successful in 1m30s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 1m26s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / no frontend) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / no frontend) (push) Successful in 1m30s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / --features danger) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / --features danger) (push) Successful in 1m29s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / --features serde) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / --features serde) (push) Successful in 1m28s
Rust CI / cargo audit (push) Successful in 4s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 13s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 13s
Rust CI / no-std (thumbv6m-none-eabi / --features ristretto255-ciphersuite) (push) Successful in 14s
Rust CI / no-std (wasm32-unknown-unknown / --features ristretto255-ciphersuite) (push) Successful in 14s
Rust CI / cargo fmt (push) Successful in 3s
Rust CI / cargo clippy (push) Successful in 26s
Rust CI / test (1.87.0 / no backend / no frontend) (push) Successful in 1m27s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 1m20s
Rust CI / test (1.87.0 / no backend / --features danger) (push) Successful in 1m30s
Rust CI / test (stable / no backend / --features danger) (push) Successful in 1m21s
Publish / publish (release) Successful in 19s
Rust CI / test (1.87.0 / no backend / --features serde) (push) Successful in 1m30s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 1m26s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / no frontend) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / no frontend) (push) Successful in 1m30s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / --features danger) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / --features danger) (push) Successful in 1m29s
Rust CI / test (1.87.0 / --features ristretto255-ciphersuite / --features serde) (push) Successful in 1m34s
Rust CI / test (stable / --features ristretto255-ciphersuite / --features serde) (push) Successful in 1m28s
Rust CI / cargo audit (push) Successful in 4s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 13s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 13s
Rust CI / no-std (thumbv6m-none-eabi / --features ristretto255-ciphersuite) (push) Successful in 14s
Rust CI / no-std (wasm32-unknown-unknown / --features ristretto255-ciphersuite) (push) Successful in 14s
Reviewed-on: #10 Co-authored-by: UneBaguette <[email protected]> Co-committed-by: UneBaguette <[email protected]>
This commit was merged in pull request #10.
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-rc.1 (July 3, 2026)
|
||||
|
||||
* Reject trailing bytes in all `deserialize` methods
|
||||
* Reject identity element in `deterministic_blind_unchecked` to prevent blinding bypass
|
||||
* Added roundtrip, trailing bytes, truncated, and empty input tests for serialization
|
||||
|
||||
## 1.0.0-rc.0 (July 2, 2026)
|
||||
|
||||
* Added missing license in Cargo manifest
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ name = "voprf-vx"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/vexahub/voprf-vx/"
|
||||
rust-version = "1.87"
|
||||
version = "1.0.0-rc.0"
|
||||
version = "1.0.0-rc.1"
|
||||
|
||||
[features]
|
||||
alloc = []
|
||||
@@ -22,7 +22,7 @@ serde = ["curve25519-dalek?/serde", "hybrid-array/serde", "dep:serde"]
|
||||
std = ["alloc"]
|
||||
|
||||
[dependencies]
|
||||
curve25519-dalek = { version = "5.0.0-rc", default-features = false, features = ["rand_core", "zeroize"], optional = true }
|
||||
curve25519-dalek = { version = "5.0.0-rc.1", default-features = false, features = ["rand_core", "zeroize"], optional = true }
|
||||
derive-where = { version = "1", features = ["zeroize-on-drop"] }
|
||||
digest = { version = "0.11", features = ["zeroize"] }
|
||||
displaydoc = { version = "0.2", default-features = false }
|
||||
@@ -36,21 +36,21 @@ serde = { version = "1", default-features = false, features = [
|
||||
"derive",
|
||||
], optional = true }
|
||||
sha2 = { version = "0.11", default-features = false, features = ["zeroize"], optional = true }
|
||||
p256 = { version = "0.14.0-rc", default-features = false, features = ["hash2curve", "oprf"], optional = true }
|
||||
p256 = { version = "0.14.0-rc.15", default-features = false, features = ["hash2curve", "oprf"], optional = true }
|
||||
subtle = { version = "2.6", default-features = false }
|
||||
zeroize = { version = "1.5", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4"
|
||||
p256 = { version = "0.14.0-rc", default-features = false, features = [
|
||||
p256 = { version = "0.14.0-rc.15", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"oprf",
|
||||
] }
|
||||
p384 = { version = "0.14.0-rc", default-features = false, features = [
|
||||
p384 = { version = "0.14.0-rc.15", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"oprf",
|
||||
] }
|
||||
p521 = { version = "0.14.0-rc", default-features = false, features = [
|
||||
p521 = { version = "0.14.0-rc.15", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"oprf",
|
||||
] }
|
||||
|
||||
@@ -16,7 +16,7 @@ Installation
|
||||
Add the following line to the dependencies of your `Cargo.toml`:
|
||||
|
||||
```
|
||||
voprf = { package = "voprf-vx", version = "1.0.0-rc.0" }
|
||||
voprf = { package = "voprf-vx", version = "1.0.0-rc.1" }
|
||||
```
|
||||
|
||||
### Minimum Supported Rust Version
|
||||
|
||||
@@ -397,6 +397,12 @@ pub(crate) fn deterministic_blind_unchecked<CS: CipherSuite>(
|
||||
mode: Mode,
|
||||
) -> Result<<CS::Group as Group>::Elem> {
|
||||
let hashed_point = hash_to_group::<CS>(input, mode)?;
|
||||
|
||||
// Identity element would nullify blinding, revealing the input.
|
||||
if CS::Group::is_identity_elem(hashed_point).into() {
|
||||
return Err(Error::Input);
|
||||
}
|
||||
|
||||
Ok(hashed_point * blind)
|
||||
}
|
||||
|
||||
|
||||
+170
-3
@@ -34,6 +34,10 @@ impl<CS: CipherSuite> OprfClient<CS> {
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self { blind })
|
||||
}
|
||||
}
|
||||
@@ -59,6 +63,10 @@ impl<CS: CipherSuite> VoprfClient<CS> {
|
||||
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
blind,
|
||||
blinded_element,
|
||||
@@ -87,6 +95,10 @@ impl<CS: CipherSuite> PoprfClient<CS> {
|
||||
let blind = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
let blinded_element = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
blind,
|
||||
blinded_element,
|
||||
@@ -110,6 +122,10 @@ impl<CS: CipherSuite> OprfServer<CS> {
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self { sk })
|
||||
}
|
||||
}
|
||||
@@ -134,6 +150,10 @@ impl<CS: CipherSuite> VoprfServer<CS> {
|
||||
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
let pk = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self { sk, pk })
|
||||
}
|
||||
}
|
||||
@@ -158,6 +178,10 @@ impl<CS: CipherSuite> PoprfServer<CS> {
|
||||
let sk = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
let pk = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self { sk, pk })
|
||||
}
|
||||
}
|
||||
@@ -183,6 +207,10 @@ impl<CS: CipherSuite> Proof<CS> {
|
||||
let c_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
let s_scalar = deserialize_scalar::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Proof { c_scalar, s_scalar })
|
||||
}
|
||||
}
|
||||
@@ -203,6 +231,10 @@ impl<CS: CipherSuite> BlindedElement<CS> {
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||
let value = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
@@ -223,6 +255,10 @@ impl<CS: CipherSuite> EvaluationElement<CS> {
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self> {
|
||||
let value = deserialize_elem::<CS::Group>(&mut input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(Error::Deserialization);
|
||||
}
|
||||
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
@@ -317,6 +353,7 @@ mod test {
|
||||
VoprfClient, VoprfServer,
|
||||
};
|
||||
|
||||
// Fuzz: no panics on arbitrary input
|
||||
macro_rules! test_deserialize {
|
||||
($item:ident, $bytes:ident) => {
|
||||
#[cfg(feature = "ristretto255")]
|
||||
@@ -324,12 +361,43 @@ mod test {
|
||||
let _ = $item::<crate::Ristretto255>::deserialize(&$bytes[..]);
|
||||
}
|
||||
|
||||
let _ = $item::<p256::NistP256>::deserialize(&$bytes[..]);
|
||||
let _ = $item::<p384::NistP384>::deserialize(&$bytes[..]);
|
||||
let _ = $item::<p521::NistP521>::deserialize(&$bytes[..]);
|
||||
let _ = $item::<::p256::NistP256>::deserialize(&$bytes[..]);
|
||||
let _ = $item::<::p384::NistP384>::deserialize(&$bytes[..]);
|
||||
let _ = $item::<::p521::NistP521>::deserialize(&$bytes[..]);
|
||||
};
|
||||
}
|
||||
|
||||
// Roundtrip: serialize to deserialize == original
|
||||
macro_rules! test_roundtrip {
|
||||
($item:ident, $cs:ty, $constructor:expr) => {{
|
||||
let original = $constructor;
|
||||
let bytes = original.serialize();
|
||||
let recovered = $item::<$cs>::deserialize(&bytes).expect("roundtrip deserialize");
|
||||
assert_eq!(original.serialize(), recovered.serialize());
|
||||
}};
|
||||
}
|
||||
|
||||
// Trailing bytes: valid serialization + extra byte must fail
|
||||
macro_rules! test_trailing {
|
||||
($item:ident, $cs:ty, $constructor:expr) => {{
|
||||
let original = $constructor;
|
||||
let bytes = original.serialize();
|
||||
let mut extended = bytes.to_vec();
|
||||
extended.push(0x00);
|
||||
assert!($item::<$cs>::deserialize(&extended).is_err());
|
||||
}};
|
||||
}
|
||||
|
||||
// Truncated: valid serialization minus one byte must fail
|
||||
macro_rules! test_truncated {
|
||||
($item:ident, $cs:ty, $constructor:expr) => {{
|
||||
let original = $constructor;
|
||||
let bytes = original.serialize();
|
||||
let truncated = &bytes[..bytes.len() - 1];
|
||||
assert!($item::<$cs>::deserialize(truncated).is_err());
|
||||
}};
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_nocrash_oprf_client(bytes in vec(any::<u8>(), 0..200)) {
|
||||
@@ -377,4 +445,103 @@ mod test {
|
||||
test_deserialize!(Proof, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! structured_tests {
|
||||
($cs:ty, $mod:ident) => {
|
||||
mod $mod {
|
||||
use super::*;
|
||||
|
||||
use rand::rngs::SysRng;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_oprf_client() {
|
||||
let client = OprfClient::<$cs>::blind(b"input", &mut SysRng)
|
||||
.expect("blind")
|
||||
.state;
|
||||
test_roundtrip!(OprfClient, $cs, client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_oprf_server() {
|
||||
let server = OprfServer::<$cs>::new(&mut SysRng).expect("new");
|
||||
test_roundtrip!(OprfServer, $cs, server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_voprf_client() {
|
||||
let client = VoprfClient::<$cs>::blind(b"input", &mut SysRng)
|
||||
.expect("blind")
|
||||
.state;
|
||||
test_roundtrip!(VoprfClient, $cs, client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_voprf_server() {
|
||||
let server = VoprfServer::<$cs>::new(&mut SysRng).expect("new");
|
||||
test_roundtrip!(VoprfServer, $cs, server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_poprf_client() {
|
||||
let client = PoprfClient::<$cs>::blind(b"input", &mut SysRng)
|
||||
.expect("blind")
|
||||
.state;
|
||||
test_roundtrip!(PoprfClient, $cs, client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_poprf_server() {
|
||||
let server = PoprfServer::<$cs>::new(&mut SysRng).expect("new");
|
||||
test_roundtrip!(PoprfServer, $cs, server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_oprf_client() {
|
||||
let client = OprfClient::<$cs>::blind(b"input", &mut SysRng)
|
||||
.expect("blind")
|
||||
.state;
|
||||
test_trailing!(OprfClient, $cs, client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_oprf_server() {
|
||||
let server = OprfServer::<$cs>::new(&mut SysRng).expect("new");
|
||||
test_trailing!(OprfServer, $cs, server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_oprf_client() {
|
||||
let client = OprfClient::<$cs>::blind(b"input", &mut SysRng)
|
||||
.expect("blind")
|
||||
.state;
|
||||
test_truncated!(OprfClient, $cs, client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_oprf_server() {
|
||||
let server = OprfServer::<$cs>::new(&mut SysRng).expect("new");
|
||||
test_truncated!(OprfServer, $cs, server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_input_fails() {
|
||||
assert!(OprfClient::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(OprfServer::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(VoprfClient::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(VoprfServer::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(PoprfClient::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(PoprfServer::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(BlindedElement::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(EvaluationElement::<$cs>::deserialize(&[]).is_err());
|
||||
assert!(Proof::<$cs>::deserialize(&[]).is_err());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
structured_tests!(crate::Ristretto255, ristretto255);
|
||||
structured_tests!(::p256::NistP256, p256);
|
||||
structured_tests!(::p384::NistP384, p384);
|
||||
structured_tests!(::p521::NistP521, p521);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user