/*
* proof_verifier.rs
* Simulating the Proof Verifier process for the zkPass Demo
*
* ---
* References:
* https://docs.ssi.id/zkpass/zkpass-developers-guide/privacy-apps/dvr/dvr-client-roles/proof-verifier
* ---
* Copyright (c) 2024 PT Darta Media Indonesia. All rights reserved.
*/
use crate::{
helper::extract_payload_without_validation,
lib_loader::{
generate_query_token,
get_dvr_id_from_proof,
verify_zkpass_proof as verify_zkpass_proof_ffi,
},
sample_keys::{ issuer_pubkey, verifier_pubkey, VERIFIER_PRIVKEY },
};
use dvr_types::{
DvrDataFfi,
ExpectedDvrMetadataFfi,
UserDataRequestFfi,
PublicKeyOptionFfi,
PublicKeyOptionTagFfi,
PublicKeyOptionUnionFfi,
KeysetEndpointFfi,
PublicKeyFfi,
};
use lazy_static::lazy_static;
use serde_json::Value;
use std::{ collections::HashMap, io::prelude::*, sync::Mutex, time::Instant, ffi::CString };
use tracing::trace;
use uuid::Uuid;
//
// Global table to store the generated DVR values
// The Verifier needs to keep track of all generated DVRs
// so that it can verify the proof metadata
// Note: The hash table entry should have time-expiration
//
lazy_static! {
static ref DVR_TABLE: Mutex<HashMap<String, String>> = {
let map = HashMap::new();
Mutex::new(map)
};
}
// This struct ensures that the data reference is valid
pub struct PublicKeyOptionHolder {
key_x: CString,
key_y: CString,
empty_str: CString,
pub public_key_option: PublicKeyOptionFfi,
}
// This struct ensures that the data reference is valid
#[allow(dead_code)]
struct UserDataRequestsHolder {
tags: Vec<CString>,
user_data_requests: Vec<UserDataRequestFfi>,
key_x: CString,
key_y: CString,
empty_str: CString,
public_key_option: PublicKeyOptionFfi,
}
//
// Simulating the Proof Verifier
//
pub struct ProofVerifier {
pub user_data_tags: Vec<String>,
}
impl Default for ProofVerifier {
fn default() -> Self {
ProofVerifier {
user_data_tags: Vec::new(),
}
}
}
impl ProofVerifier {
///
/// Generates the user data requests.
///
fn user_data_requests(&self) -> Box<UserDataRequestsHolder> {
let issuer_public_key_option_holder = self.generate_issuer_public_key_option();
let user_data_tags = self.user_data_tags.clone();
let tags: Vec<CString> = user_data_tags
.iter()
.map(|tag| CString::new(tag.as_str()).unwrap())
.collect();
let user_data_requests = tags
.iter()
.map(|tag| {
UserDataRequestFfi {
key: tag.as_ptr(),
value: issuer_public_key_option_holder.public_key_option.clone(),
}
})
.collect();
Box::new(UserDataRequestsHolder {
tags,
user_data_requests: user_data_requests,
key_x: issuer_public_key_option_holder.key_x,
key_y: issuer_public_key_option_holder.key_y,
empty_str: issuer_public_key_option_holder.empty_str,
public_key_option: issuer_public_key_option_holder.public_key_option,
})
}
///
/// Simulates the Proof Verifier's get_dvr_token REST API.
///
pub fn get_dvr_token(
&mut self,
zkvm: &str,
dvr_file: &str,
user_data_tags: Vec<&String>
) -> String {
self.user_data_tags = user_data_tags
.iter()
.cloned()
.map(|s| s.clone())
.collect();
let mut query_content = std::fs::File::open(dvr_file).expect("Cannot find the dvr file");
let mut query = String::new();
query_content.read_to_string(&mut query).expect("Should not have I/O errors");
trace!("query={}", query);
let query: Value = serde_json::from_str(&query).unwrap();
//
// Proof Verifier's integration points with the zkpass-client SDK library
// (for get_dvr_token REST API)
//
let query_string = serde_json::to_string(&query).unwrap();
let zkvm_cstring = CString::new(zkvm).unwrap();
let dvr_title_cstring = CString::new("My DVR").unwrap();
let dvr_id_cstring = CString::new(Uuid::new_v4().to_string()).unwrap();
let query_cstring = CString::new(query_string).unwrap();
let verifier_public_key_option_holder = self.generate_verifier_public_key_option();
let verifier_public_key_option = verifier_public_key_option_holder.public_key_option;
let user_data_requests_holder = self.user_data_requests();
let user_data_requests = user_data_requests_holder.user_data_requests;
let user_data_requests_slice = user_data_requests.as_slice();
//
// Step 1: Create the DVR object.
//
let dvr_data = DvrDataFfi {
zkvm: zkvm_cstring.as_ptr(),
dvr_title: dvr_title_cstring.as_ptr(),
dvr_id: dvr_id_cstring.as_ptr(),
query: query_cstring.as_ptr(),
user_data_requests: user_data_requests_slice.as_ptr(),
user_data_requests_len: user_data_requests_slice.len() as u64,
dvr_verifying_key: verifier_public_key_option,
};
//
// Step 2: Call dvr client's generate_query_token function
// to digitally-sign the dvr data.
//
let dvr_token = unsafe { generate_query_token(VERIFIER_PRIVKEY, dvr_data) };
let payload = extract_payload_without_validation(&dvr_token).unwrap();
// save the dvr to a global hash table
// this will be needed by the validator to check the proof metadata
let mut dvr_table = DVR_TABLE.lock().unwrap();
if let Some(data) = payload.get("data") {
let dvr_id = data["dvr_id"].as_str().unwrap();
dvr_table.insert(dvr_id.to_string(), data.to_string());
}
dvr_token
}
///
/// Generates the public key option.
///
pub fn generate_public_key_option(&self, is_verifier: bool) -> Box<PublicKeyOptionHolder> {
let (key_x, key_y) = if is_verifier { verifier_pubkey() } else { issuer_pubkey() };
let key_x = CString::new(key_x).unwrap();
let key_y = CString::new(key_y).unwrap();
let empty_str = CString::new("").unwrap();
let public_key_option = PublicKeyOptionFfi {
tag: PublicKeyOptionTagFfi::PublicKey,
value: PublicKeyOptionUnionFfi {
keyset_endpoint: KeysetEndpointFfi {
jku: empty_str.as_ptr(),
kid: empty_str.as_ptr(),
},
public_key: PublicKeyFfi {
x: key_x.as_ptr(),
y: key_y.as_ptr(),
},
},
};
Box::new(PublicKeyOptionHolder {
key_x,
key_y,
empty_str,
public_key_option,
})
}
///
/// Generates the verifier public key option.
///
fn generate_verifier_public_key_option(&self) -> Box<PublicKeyOptionHolder> {
self.generate_public_key_option(true)
}
///
/// Generates the issuer public key option.
///
fn generate_issuer_public_key_option(&self) -> Box<PublicKeyOptionHolder> {
self.generate_public_key_option(false)
}
//
/// Simulates the Proof Verifier's verify_zkpass_proof REST API.
///
pub async fn verify_zkpass_proof(&self, zkvm: &str, zkpass_proof_token: &str) -> String {
println!("\n#### starting zkpass proof verification...");
let start = Instant::now();
let url = std::env
::var("ZKPASS_URL")
.unwrap_or("https://staging-zkpass.ssi.id".to_string());
let some_ttl: u64 = 3600;
let dvr_id = unsafe { get_dvr_id_from_proof(zkpass_proof_token) };
let expected_dvr = DVR_TABLE.lock().unwrap().get(&dvr_id).unwrap().clone();
let expected_dvr_cstring = CString::new(expected_dvr).unwrap();
let user_data_requests_holder = self.user_data_requests();
let user_data_requests = user_data_requests_holder.user_data_requests;
let user_data_requests_slice = user_data_requests.as_slice();
//
// Step 1: Create the expected metadata object.
//
let expected_metadata = ExpectedDvrMetadataFfi {
ttl: some_ttl,
dvr: expected_dvr_cstring.as_ptr(),
user_data_verifying_keys: user_data_requests_slice.as_ptr(),
user_data_verifying_keys_len: user_data_requests_slice.len() as u64,
};
//
// Step 2: Call zkpass_client.verify_zkpass_proof to verify the proof.
//
let result = unsafe {
verify_zkpass_proof_ffi(&url, zkvm, zkpass_proof_token, expected_metadata)
};
let duration = start.elapsed();
println!("#### verification completed [time={:?}]", duration);
result
}
}
1. Providing DVR Retrieval API
Providing a REST API to retrieve the DVR
The Proof Verifier must also define a mechanism through which users can retrieve the DVR token. The retrieval could be accomplished through various means, such as QR code scanning, a RESTful API, or other methods. zkPass SDK provides the flexibility to choose a retrieval mechanism most appropriate to the application's architectural design.
Signing the DVR data
The Proof Verifier is the entity responsible for defining the Data Verification Request (DVR) against which user data is verified. To accurately create DVR queries, the verifier must have an in-depth understanding of the user data format as provided by the Data Issuer. Utilizing zkPass Query Language, the verifier can reference specific fields within the user data using the notion of 'Variable'.
Upon receipt of a Zero-Knowledge Proof (ZKP) from the user, the verifier invokes zkPass.verifyProof function to authenticate the proof. A successful verification indicates that the user's data complies with all conditions stipulated in the DVR query.
The Proof Verifier is responsible for generating tokens for the DVR content. To achieve this, it must manage a public/private key pair specifically for digital signing. Additionally, the Proof Verifier should expose a standard JWKS (JSON Web Key Set) endpoint through a RESTful API for signature verification purposes. This endpoint serves as the source for obtaining the public key required to validate the DVR token's signature.
To facilitate the zkPass flow, the verifier must tokenize the DVR object into a JWT (JSON Web Token). In the inner token’s JWT header, the following parameters will be included by the verifier:
jku
The URL where the public verification key can be retrieved.
kid
The Key ID, is a unique identifier for the specific verification key in use.
zkpass-client Integration
In the implementation of the DVR retrieval codes, the Proof Verifier needs to digitally sign the DVR. This is done using the zkpass-client SDK library as illustrated here.
...
//
// Step 1: Instantiate the zkpass_client object.
//
let zkpass_client = ZkPassClient::new(
"", // This is the zkPass service URL, we don't provide the value because it's not needed on signing DVR flow
ZkPassApiKey {
api_key: "".to_string(),
secret_api_key: "".to_string(),
},
);
//
// Step 2: Call zkpass_client.get_query_engine_version_info.
// The version info is needed for DVR object creation.
//
let query_engine_version_info = zkpass_client.get_query_engine_version_info();
//
// Step 3: Create the DVR object
//
let dvr = DataVerificationRequest {
zkvm: String::from(zkvm),
dvr_title: String::from("My DVR"),
dvr_id: Uuid::new_v4().to_string(),
query_engine_ver: query_engine_version_info.0,
query_method_ver: query_engine_version_info.1,
query: serde_json::to_string(&query).unwrap(),
user_data_requests: wrap_single_user_data_input(UserDataRequest {
user_data_url: Some(String::from("https://hostname/api/user_data/")),
user_data_verifying_key: PublicKeyOption::PublicKey(issuer_pubkey()),
}),
dvr_verifying_key: Some(PublicKeyOption::PublicKey(verifier_pubkey())),
};
//
// Step 4: Call zkpass_client.sign_data_to_jws_token.
// to digitally-sign the dvr data.
//
let dvr_token = zkpass_client
.sign_data_to_jws_token(
VERIFIER_PRIVKEY,
json!(dvr.clone()),
Some(ep))
.unwrap();
...
2. Providing Proof Verification API
Providing a REST API to verify ZkPass Proof
Dvr Module Client Integration
In the proof verification API implementation, the Proof Verifier needs to verify the received ZkPass as shown here.