use crate::{Frame, Sqs, QfeError, PatternType, Sha512Hash};
use crate::PHI;
use sha2::{Sha512, Digest};
use curve25519_dalek::{
ristretto::RistrettoPoint,
scalar::Scalar,
constants::RISTRETTO_BASEPOINT_POINT };
use rand::RngCore;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ZkpValidityResponse {
pub validity_proof_hash: Sha512Hash, }
pub fn establish_zkp_sqs(
prover_id: &str,
verifier_id: &str,
public_statement: &[u8],
context_string: &str,
) -> Result<Sqs, QfeError> {
let mut components_hasher = Sha512::new();
components_hasher.update(b"QFE_ZKP_SQS_COMPONENTS_V1");
components_hasher.update(prover_id.as_bytes());
components_hasher.update(verifier_id.as_bytes());
components_hasher.update(public_statement);
components_hasher.update(context_string.as_bytes());
components_hasher.update(PHI.to_le_bytes());
let sqs_components: Vec<u8> = components_hasher.finalize().to_vec();
if sqs_components.len() != 64 {
return Err(QfeError::InternalError(format!(
"Derived ZKP SQS components have unexpected length: {}", sqs_components.len()
)));
}
let sqs = Sqs {
pattern_type: PatternType::Sqs,
components: sqs_components, validation: true, ..Default::default()
};
Ok(sqs)
}
#[derive(Debug, Clone)] pub struct SchnorrProof {
pub r: RistrettoPoint, pub s: Scalar, }
fn hash_to_scalar(
public_point_p: &RistrettoPoint,
commitment_point_r: &RistrettoPoint,
zkp_sqs: Option<&Sqs>, context_string: Option<&[u8]>,
) -> Scalar {
let mut hasher = Sha512::new();
hasher.update(b"QFE_SCHNORR_CHALLENGE_V1"); hasher.update(RISTRETTO_BASEPOINT_POINT.compress().as_bytes()); hasher.update(public_point_p.compress().as_bytes()); hasher.update(commitment_point_r.compress().as_bytes());
if let Some(sqs) = zkp_sqs {
if sqs.validation {
let mut ids = [sqs.participant_a_id.as_bytes(), sqs.participant_b_id.as_bytes()];
ids.sort_unstable();
hasher.update(b"SQS_CONTEXT_IDS_V1"); hasher.update(ids[0]);
hasher.update(ids[1]);
} else {
hasher.update(b"INVALID_SQS_CONTEXT");
}
}
if let Some(ctx) = context_string {
hasher.update(b"EXTRA_CONTEXT_V1"); hasher.update(ctx);
}
let hash_output: [u8; 64] = hasher.finalize().into();
Scalar::from_bytes_mod_order_wide(&hash_output)
}
fn derive_fs_challenge(
zkp_sqs: &Sqs, public_statement_h_public: &[u8],
) -> Vec<u8> {
let mut challenge_hasher = Sha512::new();
challenge_hasher.update(b"QFE_ZKP_FIAT_SHAMIR_CHALLENGE_V1");
challenge_hasher.update(&zkp_sqs.components);
challenge_hasher.update(public_statement_h_public);
challenge_hasher.update(PHI.to_le_bytes()); challenge_hasher.finalize().to_vec()
}
impl Frame {
pub fn store_zkp_witness(&mut self, witness: &[u8]) -> Result<(), QfeError> {
if !self.is_valid() { return Err(QfeError::FrameInvalid); }
self.zkp_witness = Some(witness.to_vec());
Ok(())
}
pub fn generate_noninteractive_validity_proof(
&self,
zkp_sqs: &Sqs,
public_statement_h_public: &[u8],
) -> Result<ZkpValidityResponse, QfeError> {
if !self.is_valid() { return Err(QfeError::FrameInvalid); }
if !zkp_sqs.validation { return Err(QfeError::InternalError("Invalid ZKP SQS provided for proof".to_string())); }
let witness_w = self.zkp_witness.as_ref().ok_or_else(|| QfeError::InternalError("ZKP witness not set for proof generation".to_string()))?;
let calculated_hash_of_w: Sha512Hash = Sha512::digest(witness_w).into();
let is_valid_witness: bool = calculated_hash_of_w.as_slice() == public_statement_h_public;
let derived_challenge_value = derive_fs_challenge(zkp_sqs, public_statement_h_public);
let mut response_hasher = Sha512::new();
response_hasher.update(b"QFE_ZKP_VALIDITY_PROOF_V1"); response_hasher.update(&derived_challenge_value); response_hasher.update([is_valid_witness as u8]); response_hasher.update(&zkp_sqs.components);
response_hasher.update(PHI.to_le_bytes());
let proof_hash: Sha512Hash = response_hasher.finalize().into();
Ok(ZkpValidityResponse { validity_proof_hash: proof_hash })
}
pub fn verify_noninteractive_validity_proof(
&mut self, response: &ZkpValidityResponse,
zkp_sqs: &Sqs,
public_statement_h_public: &[u8],
) -> Result<(), QfeError> {
if !self.is_valid() { return Err(QfeError::FrameInvalid); }
if !zkp_sqs.validation { return Err(QfeError::InternalError("Invalid ZKP SQS provided for verification".to_string())); }
let derived_challenge_value = derive_fs_challenge(zkp_sqs, public_statement_h_public);
let expected_hash = {
let mut response_hasher = Sha512::new();
response_hasher.update(b"QFE_ZKP_VALIDITY_PROOF_V1"); response_hasher.update(&derived_challenge_value); response_hasher.update([true as u8]); response_hasher.update(&zkp_sqs.components);
response_hasher.update(PHI.to_le_bytes());
let hash: Sha512Hash = response_hasher.finalize().into();
hash
};
if response.validity_proof_hash != expected_hash {
self.validation_status = false; return Err(QfeError::DecodingFailed(
"ZKP Non-Interactive Validity Proof Check Failed".to_string() ));
}
Ok(())
}
pub fn store_zkp_scalar(&mut self, secret_x: Scalar) -> Result<(), QfeError> {
if !self.is_valid() { return Err(QfeError::FrameInvalid); }
self.zkp_secret_scalar = Some(secret_x);
Ok(())
}
pub fn generate_schnorr_proof(
&self,
public_point_p: &RistrettoPoint,
zkp_sqs: Option<&Sqs>,
context_string: Option<&[u8]>,
) -> Result<SchnorrProof, QfeError> {
if !self.is_valid() { return Err(QfeError::FrameInvalid); }
let secret_x = self.zkp_secret_scalar.ok_or_else(|| QfeError::InternalError("ZKP secret scalar x not set".to_string()))?;
let mut data = [0u8; 32];
rand::rng().fill_bytes(&mut data);
let k = Scalar::from_bytes_mod_order(data);
let point_r = k * RISTRETTO_BASEPOINT_POINT;
let c = hash_to_scalar(public_point_p, &point_r, zkp_sqs, context_string);
let s = k + c * secret_x;
Ok(SchnorrProof { r: point_r, s })
}
}
pub fn verify_schnorr_proof(
proof: &SchnorrProof,
public_point_p: &RistrettoPoint,
zkp_sqs: Option<&Sqs>,
context_string: Option<&[u8]>,
) -> Result<(), QfeError> {
let c = hash_to_scalar(public_point_p, &proof.r, zkp_sqs, context_string);
let lhs = proof.s * RISTRETTO_BASEPOINT_POINT;
let rhs = proof.r + c * public_point_p;
if lhs == rhs {
Ok(()) } else {
Err(QfeError::DecodingFailed("Schnorr proof verification failed".to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*; use crate::{Frame}; use sha2::{Sha512, Digest};
#[allow(dead_code)]
struct SimpleZkpTestData {
prover: Frame,
verifier: Frame,
zkp_sqs: Sqs,
witness: Vec<u8>,
h_public: Sha512Hash, context: String,
}
fn setup_simple_zkp_test() -> SimpleZkpTestData {
let mut prover = Frame::initialize("ValidityProverNI".to_string()); let verifier = Frame::initialize("ValidityVerifierNI".to_string());
let witness = b"a_valid_witness_for_noninteractive_zkp".to_vec();
let h_public: Sha512Hash = Sha512::digest(&witness).into(); prover.store_zkp_witness(&witness).expect("Failed to store witness");
let context = "simple_validity_test_noninteractive_v1".to_string(); let zkp_sqs = establish_zkp_sqs( prover.id(),
verifier.id(),
&h_public,
&context,
).expect("Failed to establish ZKP SQS");
let zkp_sqs_clone = zkp_sqs.clone();
SimpleZkpTestData {
prover,
verifier,
zkp_sqs: zkp_sqs_clone,
witness,
h_public,
context,
}
}
#[test]
fn test_ni_zkp_successful_proof() {
let test_data = setup_simple_zkp_test();
let prover = test_data.prover;
let mut verifier = test_data.verifier; let zkp_sqs = test_data.zkp_sqs;
let h_public = test_data.h_public;
let response = prover.generate_noninteractive_validity_proof(
&zkp_sqs,
&h_public,
).expect("Prover failed to generate non-interactive validity proof");
let verification_result = verifier.verify_noninteractive_validity_proof(
&response,
&zkp_sqs,
&h_public,
);
assert!(verification_result.is_ok(), "Verification failed unexpectedly: {:?}", verification_result.err());
assert!(verifier.is_valid(), "Verifier should remain valid after successful verification");
}
#[test]
fn test_ni_zkp_invalid_witness() {
let test_data = setup_simple_zkp_test();
let mut prover = test_data.prover; let mut verifier = test_data.verifier;
let zkp_sqs = test_data.zkp_sqs;
let h_public = test_data.h_public;
let wrong_witness = b"this_is_the_wrong_witness_for_ni".to_vec();
prover.store_zkp_witness(&wrong_witness).expect("Storing wrong witness failed");
let response = prover.generate_noninteractive_validity_proof(
&zkp_sqs,
&h_public,
).expect("Prover failed proof generation (using wrong witness)");
let verification_result = verifier.verify_noninteractive_validity_proof(
&response,
&zkp_sqs,
&h_public,
);
assert!(verification_result.is_err(), "Verification should fail for invalid witness");
let err = verification_result.unwrap_err();
assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed, got {:?}", err);
if let QfeError::DecodingFailed(msg) = err {
assert!(msg.contains("Non-Interactive Validity Proof Check Failed"), "Expected NI Validity Proof failure message, got: {}", msg);
}
assert!(!verifier.is_valid(), "Verifier should become invalid after failed verification");
}
#[test]
fn test_ni_zkp_tampered_response_hash() {
let test_data = setup_simple_zkp_test();
let prover = test_data.prover;
let mut verifier = test_data.verifier;
let zkp_sqs = test_data.zkp_sqs;
let h_public = test_data.h_public;
let mut response = prover.generate_noninteractive_validity_proof(&zkp_sqs, &h_public)
.expect("Prover failed proof generation");
response.validity_proof_hash[0] ^= 0xAA;
let verification_result = verifier.verify_noninteractive_validity_proof(
&response, &zkp_sqs,
&h_public,
);
assert!(verification_result.is_err(), "Verification should fail for tampered validity proof hash");
let err = verification_result.unwrap_err();
assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed, got {:?}", err);
if let QfeError::DecodingFailed(msg) = err {
assert!(msg.contains("Non-Interactive Validity Proof Check Failed"), "Expected NI Validity Proof failure message, got: {}", msg);
}
assert!(!verifier.is_valid());
}
#[test]
fn test_ni_zkp_wrong_sqs() {
let test_data1 = setup_simple_zkp_test(); let prover = test_data1.prover;
let zkp_sqs1 = test_data1.zkp_sqs;
let h_public = test_data1.h_public;
let mut verifier2 = Frame::initialize("Verifier2_WrongSQS_NI".to_string());
let zkp_sqs2 = establish_zkp_sqs(
prover.id(),
verifier2.id(), &h_public,
"a_completely_different_context_ni", ).expect("Failed to establish ZKP SQS2");
assert_ne!(zkp_sqs1.components, zkp_sqs2.components);
let response = prover.generate_noninteractive_validity_proof(&zkp_sqs1, &h_public)
.expect("Prover failed proof generation");
let verification_result = verifier2.verify_noninteractive_validity_proof(
&response,
&zkp_sqs2, &h_public,
);
assert!(verification_result.is_err(), "Verification should fail when using wrong SQS");
let err = verification_result.unwrap_err();
assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed, got {:?}", err);
if let QfeError::DecodingFailed(msg) = err {
assert!(msg.contains("Non-Interactive Validity Proof Check Failed"), "Expected NI Validity Proof failure message, got: {}", msg);
}
assert!(!verifier2.is_valid());
}
#[test]
fn test_ni_zkp_wrong_public_statement() {
let test_data = setup_simple_zkp_test();
let prover = test_data.prover;
let mut verifier = test_data.verifier;
let zkp_sqs = test_data.zkp_sqs; let h_public_correct = test_data.h_public;
let h_public_wrong: [u8; 64] = Sha512::digest(b"some other public data").try_into().unwrap();
assert_ne!(h_public_correct.as_slice(), h_public_wrong.as_slice());
let response = prover.generate_noninteractive_validity_proof(
&zkp_sqs,
&h_public_correct, ).expect("Prover failed proof generation");
let verification_result = verifier.verify_noninteractive_validity_proof(
&response,
&zkp_sqs, &h_public_wrong, );
assert!(verification_result.is_err(), "Verification should fail when Verifier uses wrong H_public for challenge derivation");
let err = verification_result.unwrap_err();
assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed, got {:?}", err);
if let QfeError::DecodingFailed(msg) = err {
assert!(msg.contains("Non-Interactive Validity Proof Check Failed"), "Expected NI Validity Proof failure message, got: {}", msg);
}
assert!(!verifier.is_valid());
}
}
#[cfg(test)]
mod schnorr_tests { use super::*; use crate::{Frame, establish_zkp_sqs}; use curve25519_dalek::{scalar::Scalar, constants::RISTRETTO_BASEPOINT_POINT};
use rand::RngCore;
fn setup_schnorr_test() -> (Frame, Scalar, RistrettoPoint, Option<Sqs>) {
let mut prover = Frame::initialize("SchnorrProver".to_string());
let verifier_id = "SchnorrVerifier";
let mut data = [0u8; 32];
rand::rng().fill_bytes(&mut data);
let secret_x = Scalar::from_bytes_mod_order(data);
let public_p = secret_x * RISTRETTO_BASEPOINT_POINT;
prover.store_zkp_scalar(secret_x).expect("Failed to store scalar");
let sqs_context_string = "schnorr_sqs_test_v1";
let sqs = establish_zkp_sqs(
prover.id(),
verifier_id,
public_p.compress().as_bytes(),
sqs_context_string
).expect("Failed to establish Schnorr SQS");
(prover, secret_x, public_p, Some(sqs))
}
#[test]
fn test_schnorr_proof_successful() {
let (prover, _secret_x, public_p, sqs_opt) = setup_schnorr_test();
let sqs_ref = sqs_opt.as_ref();
let proof = prover.generate_schnorr_proof(&public_p, sqs_ref, None)
.expect("Prover failed to generate Schnorr proof");
let verification_result = verify_schnorr_proof(&proof, &public_p, sqs_ref, None);
assert!(verification_result.is_ok(), "Schnorr verification failed unexpectedly");
}
#[test]
fn test_schnorr_proof_invalid_proof_s() {
let (prover, _secret_x, public_p, sqs_opt) = setup_schnorr_test();
let sqs_ref = sqs_opt.as_ref();
let mut proof = prover.generate_schnorr_proof(&public_p, sqs_ref, None)
.expect("Prover failed to generate Schnorr proof");
proof.s = proof.s + Scalar::ONE;
let verification_result = verify_schnorr_proof(&proof, &public_p, sqs_ref, None);
assert!(verification_result.is_err(), "Schnorr verification should fail for tampered s");
assert!(matches!(verification_result.unwrap_err(), QfeError::DecodingFailed(_)));
}
#[test]
fn test_schnorr_proof_invalid_proof_r() {
let (prover, _secret_x, public_p, sqs_opt) = setup_schnorr_test();
let sqs_ref = sqs_opt.as_ref();
let mut proof = prover.generate_schnorr_proof(&public_p, sqs_ref, None)
.expect("Prover failed to generate Schnorr proof");
proof.r = RISTRETTO_BASEPOINT_POINT;
let verification_result = verify_schnorr_proof(&proof, &public_p, sqs_ref, None);
assert!(verification_result.is_err(), "Schnorr verification should fail for tampered R");
assert!(matches!(verification_result.unwrap_err(), QfeError::DecodingFailed(_)));
}
#[test]
fn test_schnorr_proof_wrong_public_point() {
let (prover, _secret_x, public_p, sqs_opt) = setup_schnorr_test();
let sqs_ref = sqs_opt.as_ref();
let proof = prover.generate_schnorr_proof(&public_p, sqs_ref, None)
.expect("Prover failed to generate Schnorr proof");
let wrong_public_p = RISTRETTO_BASEPOINT_POINT;
let verification_result = verify_schnorr_proof(&proof, &wrong_public_p, sqs_ref, None);
assert!(verification_result.is_err(), "Schnorr verification should fail for wrong public point P");
assert!(matches!(verification_result.unwrap_err(), QfeError::DecodingFailed(_)));
}
#[test]
fn test_schnorr_proof_missing_scalar() {
let (mut prover_no_scalar, _secret_x, public_p, sqs_opt) = setup_schnorr_test();
prover_no_scalar.zkp_secret_scalar = None; let sqs_ref = sqs_opt.as_ref();
let proof_result = prover_no_scalar.generate_schnorr_proof(&public_p, sqs_ref, None);
assert!(proof_result.is_err(), "Proof generation should fail if scalar is not set");
let err = proof_result.unwrap_err();
assert!(matches!(err, QfeError::InternalError(_)), "Expected InternalError for missing scalar, got {:?}", err);
if let QfeError::InternalError(msg) = err {
assert!(msg.contains("ZKP secret scalar x not set"));
}
}
}