use crate::{DIDWebVHError, SCID_HOLDER, parameters::Parameters};
use affinidi_data_integrity::DataIntegrityProof;
use affinidi_secrets_resolver::secrets::Secret;
use chrono::Utc;
use multibase::Base;
use multihash::Multihash;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json_canonicalizer::to_string;
use sha2::{Digest, Sha256};
use tracing::debug;
pub mod read;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LogEntry {
pub version_id: String,
pub version_time: String,
pub parameters: Parameters,
pub state: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<DataIntegrityProof>,
}
impl LogEntry {
pub async fn create_first_entry(
version_time: Option<String>,
document: &Value,
parameters: &Parameters,
secret: &Secret,
) -> Result<LogEntry, DIDWebVHError> {
let now = Utc::now();
let mut parameters = parameters.clone();
parameters.scid = Some(SCID_HOLDER.to_string());
let vm_id = if let Some(Some(value)) = ¶meters.update_keys {
if let Some(key) = value.iter().next() {
["did:key:", key, "#", key].concat()
} else {
return Err(DIDWebVHError::SCIDError(
"No update keys provided in parameters".to_string(),
));
}
} else {
return Err(DIDWebVHError::SCIDError(
"No update keys provided in parameters".to_string(),
));
};
if secret.id != vm_id {
return Err(DIDWebVHError::SCIDError(format!(
"Secret key ID {} does not match VerificationMethod ID {}",
secret.id, vm_id
)));
}
let log_entry = LogEntry {
version_id: SCID_HOLDER.to_string(),
version_time: version_time
.unwrap_or_else(|| now.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)),
parameters,
state: document.clone(),
proof: None,
};
let scid = log_entry.generate_scid()?;
let le_str = serde_json::to_string(&log_entry).map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't serialize LogEntry to JSON. Reason: {}",
e
))
})?;
let mut log_entry: LogEntry = serde_json::from_str(&le_str.replace(SCID_HOLDER, &scid))
.map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't deserialize LogEntry from SCID conversion. Reason: {}",
e
))
})?;
let entry_hash = log_entry.generate_log_entry_hash().map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate entryHash for first LogEntry. Reason: {}",
e
))
})?;
log_entry.version_id = ["1-", &entry_hash].concat();
let log_entry_values = serde_json::to_value(&log_entry).map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't convert LogEntry to JSON Values for Signing. Reason: {}",
e
))
})?;
let log_entry = serde_json::from_value(
DataIntegrityProof::sign_data_jcs(
&serde_json::from_value(log_entry_values).map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't convert LogEntry to JSON Values for Signing. Reason: {}",
e
))
})?,
&vm_id,
secret,
)
.map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate Data Integrity Proof for LogEntry. Reason: {}",
e
))
})?,
)
.map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't deserialize signed LogEntry. Reason: {}",
e
))
})?;
Ok(log_entry)
}
fn generate_scid(&self) -> Result<String, DIDWebVHError> {
self.generate_log_entry_hash().map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate SCID from preliminary LogEntry. Reason: {}",
e
))
})
}
pub fn generate_log_entry_hash(&self) -> Result<String, DIDWebVHError> {
let jcs = to_string(self).map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate JCS from LogEntry. Reason: {}",
e
))
})?;
debug!("JCS for LogEntry hash: {}", jcs);
let hash_encoded = Multihash::<32>::wrap(0x12, Sha256::digest(jcs.as_bytes()).as_slice())
.map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't create multihash encoding for LogEntry. Reason: {}",
e
))
})?;
Ok(multibase::encode(Base::Base58Btc, hash_encoded.to_bytes()))
}
pub fn revoke(&self) -> Result<Vec<LogEntry>, DIDWebVHError> {
let mut revoked_entry: LogEntry = self.clone();
revoked_entry.proof = None;
revoked_entry.parameters.deactivated = true;
revoked_entry.parameters.update_keys = Some(None);
Ok(Vec::new())
}
}