use serde_json::{Map, Value};
use dpp_crypto::keystore::KeyStore;
use dpp_crypto::sd_jwt::{Disclosure, SdJwt, build_payload, conceal};
use dpp_domain::Disclosure as DisclosureClass;
use dpp_domain::access::{DocumentScope, ProductGroupAccessPolicy};
use super::TYP;
use super::error::SdJwtVcError;
const PRODUCT_GROUP_DATA: &str = "productGroupData";
const NEVER_CONCEALED: [&str; 8] = ["iss", "vct", "iat", "_sd_alg", "aud", "exp", "nbf", "cnf"];
pub fn issue(
store: &KeyStore,
key_id: &str,
payload: &Value,
policy: &ProductGroupAccessPolicy,
issuer: &str,
vct: &str,
issued_at: i64,
) -> Result<SdJwt, SdJwtVcError> {
let Some(object) = payload.as_object() else {
return Err(SdJwtVcError::PayloadNotAnObject);
};
let mut claims = Map::new();
claims.insert("iss".to_owned(), Value::String(issuer.to_owned()));
claims.insert("vct".to_owned(), Value::String(vct.to_owned()));
claims.insert("iat".to_owned(), Value::Number(issued_at.into()));
let mut disclosures = Vec::new();
let mut segments: Vec<String> = Vec::new();
let concealed = conceal_object(
object,
policy,
DocumentScope::Envelope,
&mut segments,
&mut disclosures,
)?;
for (key, value) in concealed {
claims.entry(key).or_insert(value);
}
let jwt_payload = build_payload(claims, !disclosures.is_empty());
let jwt = dpp_crypto::jws::sign_typed(store, key_id, &jwt_payload, Some(TYP))
.map_err(|e| SdJwtVcError::Signing(e.to_string()))?;
Ok(SdJwt::new(jwt, disclosures))
}
fn conceal_object(
object: &Map<String, Value>,
policy: &ProductGroupAccessPolicy,
scope: DocumentScope,
segments: &mut Vec<String>,
disclosures: &mut Vec<Disclosure>,
) -> Result<Map<String, Value>, SdJwtVcError> {
let mut hide = Vec::new();
for key in object.keys() {
if segments.is_empty() && NEVER_CONCEALED.contains(&key.as_str()) {
continue;
}
segments.push(key.clone());
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
let class = policy.disclosure_for_path(&borrowed, scope);
drop(borrowed);
segments.pop();
if class != DisclosureClass::Public {
hide.push(key.clone());
}
}
let (mut kept, mut produced) = conceal(object, |name| hide.iter().any(|h| h == name))?;
disclosures.append(&mut produced);
for (key, value) in kept.iter_mut() {
if key == "_sd" {
continue;
}
let child_scope = if scope == DocumentScope::ProductGroupData || key == PRODUCT_GROUP_DATA {
DocumentScope::ProductGroupData
} else {
DocumentScope::Envelope
};
segments.push(key.clone());
descend(value, policy, child_scope, segments, disclosures)?;
segments.pop();
}
Ok(kept)
}
fn descend(
value: &mut Value,
policy: &ProductGroupAccessPolicy,
scope: DocumentScope,
segments: &mut Vec<String>,
disclosures: &mut Vec<Disclosure>,
) -> Result<(), SdJwtVcError> {
match value {
Value::Object(map) => {
*map = conceal_object(map, policy, scope, segments, disclosures)?;
}
Value::Array(items) => {
for item in items {
descend(item, policy, scope, segments, disclosures)?;
}
}
_ => {}
}
Ok(())
}