use crate::error::{FastCryptoError, FastCryptoResult};
use crate::groups::MultiScalarMul;
use crate::groups::{FiatShamirChallenge, GroupElement, Scalar};
use crate::traits::AllowedRng;
use serde::{Deserialize, Deserializer, Serialize};
use std::ops::Neg;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct DdhTupleNizk<G: GroupElement + Serialize + for<'d> Deserialize<'d>> {
#[serde(deserialize_with = "check_non_zero")]
a: G,
#[serde(deserialize_with = "check_non_zero")]
b: G,
#[serde(deserialize_with = "check_non_zero")]
z: G::ScalarType,
}
impl<G> DdhTupleNizk<G>
where
G: MultiScalarMul + Serialize + for<'d> Deserialize<'d>,
G::ScalarType: FiatShamirChallenge,
{
pub fn create<R: AllowedRng>(
x: &G::ScalarType,
g: &G,
h: &G,
x_g: &G,
x_h: &G,
dst: &[u8],
rng: &mut R,
) -> Self {
let r = G::ScalarType::rand(rng);
let a = *g * r;
let b = *h * r;
let challenge = Self::challenge(g, h, x_g, x_h, &a, &b, dst);
let z = challenge * x + r;
DdhTupleNizk { a, b, z }
}
pub fn verify(&self, g: &G, h: &G, x_g: &G, x_h: &G, dst: &[u8]) -> FastCryptoResult<()> {
if *g == G::zero() || *h == G::zero() || *x_g == G::zero() || *x_h == G::zero() {
return Err(FastCryptoError::InvalidProof);
}
let challenge = Self::challenge(g, h, x_g, x_h, &self.a, &self.b, dst);
if !is_valid_relation(&self.a, x_g, g, &self.z, &challenge)
|| !is_valid_relation(
&self.b, x_h, h, &self.z, &challenge,
)
{
Err(FastCryptoError::InvalidProof)
} else {
Ok(())
}
}
fn challenge(g: &G, h: &G, x_g: &G, x_h: &G, a: &G, b: &G, dst: &[u8]) -> G::ScalarType {
let chunks: Vec<Vec<u8>> = vec![
dst.to_vec(),
bcs::to_bytes(g).expect("Serialization succeeds"),
bcs::to_bytes(h).expect("Serialization succeeds"),
bcs::to_bytes(x_g).expect("Serialization succeeds"),
bcs::to_bytes(x_h).expect("Serialization succeeds"),
bcs::to_bytes(a).expect("Serialization succeeds"),
bcs::to_bytes(b).expect("Serialization succeeds"),
];
G::ScalarType::fiat_shamir_reduction_to_group_element(
&bcs::to_bytes(&chunks).expect("Serialization succeeds"),
)
}
}
fn is_valid_relation<G: MultiScalarMul>(
e1: &G,
e2: &G,
e3: &G,
z: &G::ScalarType,
c: &G::ScalarType,
) -> bool {
*e1 == G::multi_scalar_mul(&[c.neg(), *z], &[*e2, *e3]).unwrap()
}
fn check_non_zero<'de, D, G: GroupElement + Deserialize<'de>>(d: D) -> Result<G, D::Error>
where
D: Deserializer<'de>,
{
let g: G = G::deserialize(d)?;
if g == G::zero() {
Err(serde::de::Error::custom("zero element"))
} else {
Ok(g)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoding::{Encoding, Hex};
use crate::groups::ristretto255::RistrettoPoint as G;
use crate::groups::ristretto255::RistrettoScalar as S;
use crate::serde_helpers::ToFromByteArray;
use rand::thread_rng;
#[test]
fn test_nizk_flow() {
let dst = b"test";
let e = S::rand(&mut thread_rng());
let x = S::rand(&mut thread_rng());
let g = G::generator() * S::rand(&mut thread_rng());
let h = g * e;
let x_g = g * x;
let x_h = h * x;
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
assert!(nizk.verify(&g, &h, &x_g, &x_h, dst).is_ok());
assert!(nizk.verify(&g, &h, &x_g, &x_h, b"other").is_err());
let invalid_witness = x + S::generator();
let invalid_nizk =
DdhTupleNizk::create(&invalid_witness, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
assert!(invalid_nizk.verify(&g, &h, &x_g, &x_h, dst).is_err());
let other_g = g + G::generator();
assert!(nizk.verify(&other_g, &h, &x_g, &x_h, dst).is_err());
let other_h = h + G::generator();
assert!(nizk.verify(&g, &other_h, &x_g, &x_h, dst).is_err());
let other_x_g = x_g + G::generator();
assert!(nizk.verify(&g, &h, &other_x_g, &x_h, dst).is_err());
let other_x_h = x_h + G::generator();
assert!(nizk.verify(&g, &h, &x_g, &other_x_h, dst).is_err());
}
#[test]
fn challenge_regression_test() {
let dst = b"test";
let x = S::from(31u64);
let g = G::generator() * S::from(71u64);
let h = g * S::from(7u64);
let x_g = g * x;
let x_h = h * x;
let r = S::from(91u64);
let a = g * r;
let b = h * r;
let c = DdhTupleNizk::<G>::challenge(&g, &h, &x_g, &x_h, &a, &b, dst);
assert_eq!(
&c.to_byte_array(),
Hex::decode("22605808865698055d1f48c40db0af96d1f3b2b335bdd9d636e0b66b5871ef00")
.unwrap()
.as_slice()
);
let other = DdhTupleNizk::<G>::challenge(&g, &h, &x_g, &x_h, &a, &b, b"other");
assert_ne!(c.to_byte_array(), other.to_byte_array());
}
#[test]
fn test_invalid_proofs() {
let dst = b"test";
let e = S::zero();
let x = S::rand(&mut thread_rng());
let g = G::generator() * S::rand(&mut thread_rng());
let h = g * e;
let x_g = g * x;
let x_h = h * x;
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
assert!(nizk.verify(&G::zero(), &h, &x_g, &x_h, dst).is_err());
assert!(nizk.verify(&g, &G::zero(), &x_g, &x_h, dst).is_err());
assert!(nizk.verify(&g, &h, &G::zero(), &x_h, dst).is_err());
assert!(nizk.verify(&g, &h, &x_g, &G::zero(), dst).is_err());
}
#[test]
fn test_serde() {
let dst = b"test";
let e = S::rand(&mut thread_rng());
let x2 = S::rand(&mut thread_rng());
let g = G::generator() * S::rand(&mut thread_rng());
let h = g * e;
let x_g = g * x2;
let x_h = h * x2;
let nizk = DdhTupleNizk::create(&x2, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
assert!(nizk.verify(&g, &h, &x_g, &x_h, dst).is_ok());
let as_bytes = bcs::to_bytes(&nizk).unwrap();
let nizk2: DdhTupleNizk<G> = bcs::from_bytes(&as_bytes).unwrap();
assert_eq!(nizk, nizk2);
let as_bytes = bcs::to_bytes(&(G::generator(), G::generator(), S::generator())).unwrap();
assert!(bcs::from_bytes::<DdhTupleNizk<G>>(&as_bytes).is_ok());
let as_bytes = bcs::to_bytes(&(G::zero(), G::generator(), S::generator())).unwrap();
assert!(bcs::from_bytes::<DdhTupleNizk<G>>(&as_bytes).is_err());
let as_bytes = bcs::to_bytes(&(G::generator(), G::zero(), S::generator())).unwrap();
assert!(bcs::from_bytes::<DdhTupleNizk<G>>(&as_bytes).is_err());
let as_bytes = bcs::to_bytes(&(G::generator(), G::generator(), S::zero())).unwrap();
assert!(bcs::from_bytes::<DdhTupleNizk<G>>(&as_bytes).is_err());
}
}