use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const VC_CONTEXT_V1: &str = "https://www.w3.org/2018/credentials/v1";
pub const KWAAI_CONTEXT_V1: &str = "https://kwaai.ai/credentials/v1";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KwaaiCredentialType {
SummitAttendeeVC,
FiduciaryPledgeVC,
VerifiedNodeVC,
UptimeVC,
ThroughputVC,
PeerEndorsementVC,
BindingVC,
}
impl KwaaiCredentialType {
pub fn as_str(&self) -> &'static str {
match self {
Self::SummitAttendeeVC => "SummitAttendeeVC",
Self::FiduciaryPledgeVC => "FiduciaryPledgeVC",
Self::VerifiedNodeVC => "VerifiedNodeVC",
Self::UptimeVC => "UptimeVC",
Self::ThroughputVC => "ThroughputVC",
Self::PeerEndorsementVC => "PeerEndorsementVC",
Self::BindingVC => "BindingVC",
}
}
pub fn trust_weight(&self) -> f64 {
match self {
Self::FiduciaryPledgeVC => 0.30,
Self::VerifiedNodeVC => 0.20,
Self::UptimeVC => 0.20,
Self::ThroughputVC => 0.15,
Self::SummitAttendeeVC => 0.10,
Self::PeerEndorsementVC => 0.05,
Self::BindingVC => 0.0,
}
}
pub fn from_type_str(s: &str) -> Option<Self> {
match s {
"SummitAttendeeVC" => Some(Self::SummitAttendeeVC),
"FiduciaryPledgeVC" => Some(Self::FiduciaryPledgeVC),
"VerifiedNodeVC" => Some(Self::VerifiedNodeVC),
"UptimeVC" => Some(Self::UptimeVC),
"ThroughputVC" => Some(Self::ThroughputVC),
"PeerEndorsementVC" => Some(Self::PeerEndorsementVC),
"BindingVC" => Some(Self::BindingVC),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CredentialSubject {
pub id: String,
#[serde(flatten)]
pub claims: HashMap<String, serde_json::Value>,
}
impl CredentialSubject {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
claims: HashMap::new(),
}
}
pub fn with_claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.claims.insert(key.into(), value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CredentialProof {
#[serde(rename = "type")]
pub proof_type: String,
pub created: DateTime<Utc>,
#[serde(rename = "verificationMethod")]
pub verification_method: String,
#[serde(rename = "proofPurpose")]
pub proof_purpose: String,
#[serde(rename = "proofValue")]
pub proof_value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifiableCredential {
#[serde(rename = "@context")]
pub context: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type")]
pub credential_type: Vec<String>,
pub issuer: String,
#[serde(rename = "issuanceDate")]
pub issuance_date: DateTime<Utc>,
#[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")]
pub expiration_date: Option<DateTime<Utc>>,
#[serde(rename = "credentialSubject")]
pub subject: CredentialSubject,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<CredentialProof>,
}
impl VerifiableCredential {
pub fn new(
issuer: impl Into<String>,
subject: CredentialSubject,
credential_types: Vec<String>,
) -> Self {
Self {
context: vec![VC_CONTEXT_V1.to_string(), KWAAI_CONTEXT_V1.to_string()],
id: None,
credential_type: credential_types,
issuer: issuer.into(),
issuance_date: Utc::now(),
expiration_date: None,
subject,
proof: None,
}
}
pub fn kwaai_type(&self) -> Option<KwaaiCredentialType> {
self.credential_type
.iter()
.find_map(|t| KwaaiCredentialType::from_type_str(t))
}
pub fn is_expired(&self) -> bool {
self.expiration_date
.map(|exp| exp < Utc::now())
.unwrap_or(false)
}
pub fn subject_did(&self) -> &str {
&self.subject.id
}
pub fn issuer_did(&self) -> &str {
&self.issuer
}
pub fn to_compact_json(&self) -> anyhow::Result<String> {
Ok(serde_json::to_string(self)?)
}
pub fn from_json(s: &str) -> anyhow::Result<Self> {
Ok(serde_json::from_str(s)?)
}
pub fn to_signing_bytes(&self) -> anyhow::Result<Vec<u8>> {
let mut unsigned = self.clone();
unsigned.proof = None;
Ok(serde_json::to_string(&unsigned)?.into_bytes())
}
}
pub fn summit_attendee_vc(
issuer_did: impl Into<String>,
subject_did: impl Into<String>,
event_name: impl Into<String>,
event_date: impl Into<String>,
) -> VerifiableCredential {
let subject = CredentialSubject::new(subject_did)
.with_claim("eventName", serde_json::Value::String(event_name.into()))
.with_claim("eventDate", serde_json::Value::String(event_date.into()));
let mut vc = VerifiableCredential::new(
issuer_did,
subject,
vec![
"VerifiableCredential".to_string(),
"SummitAttendeeVC".to_string(),
],
);
vc.expiration_date = Some(Utc::now() + chrono::Duration::days(730));
vc
}
pub fn fiduciary_pledge_vc(
issuer_did: impl Into<String>,
subject_did: impl Into<String>,
pledge_hash: impl Into<String>,
) -> VerifiableCredential {
let subject = CredentialSubject::new(subject_did)
.with_claim("pledgeHash", serde_json::Value::String(pledge_hash.into()))
.with_claim(
"pledgeName",
serde_json::Value::String("GliaNet Fiduciary Pledge v1".to_string()),
);
VerifiableCredential::new(
issuer_did,
subject,
vec![
"VerifiableCredential".to_string(),
"FiduciaryPledgeVC".to_string(),
],
)
}
pub fn peer_endorsement_vc(
issuer_did: impl Into<String>,
subject_did: impl Into<String>,
interaction_count: u64,
) -> VerifiableCredential {
let subject = CredentialSubject::new(subject_did).with_claim(
"interactionCount",
serde_json::Value::Number(interaction_count.into()),
);
let mut vc = VerifiableCredential::new(
issuer_did,
subject,
vec![
"VerifiableCredential".to_string(),
"PeerEndorsementVC".to_string(),
],
);
vc.expiration_date = Some(Utc::now() + chrono::Duration::days(90));
vc
}
pub fn binding_vc(
issuer_did: impl Into<String>,
node_did: impl Into<String>,
passkey_did: impl Into<String>,
) -> VerifiableCredential {
let passkey_did_str = passkey_did.into();
let subject = CredentialSubject::new(node_did)
.with_claim("linkedIdentity", serde_json::Value::String(passkey_did_str))
.with_claim(
"linkType",
serde_json::Value::String("PasskeyBinding".to_string()),
);
VerifiableCredential::new(
issuer_did,
subject,
vec!["VerifiableCredential".to_string(), "BindingVC".to_string()],
)
}