pub mod canonicalization;
pub mod signature;
use crate::{Did, DidResult, Proof, ProofPurpose};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedGraph {
pub graph_uri: String,
pub triples: Vec<RdfTriple>,
pub issuer: Did,
pub issued_at: DateTime<Utc>,
pub proof: Proof,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RdfTriple {
pub subject: RdfTerm,
pub predicate: String,
pub object: RdfTerm,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum RdfTerm {
Iri(String),
BlankNode(String),
Literal {
value: String,
#[serde(skip_serializing_if = "Option::is_none")]
datatype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>,
},
}
impl RdfTriple {
pub fn new(subject: RdfTerm, predicate: &str, object: RdfTerm) -> Self {
Self {
subject,
predicate: predicate.to_string(),
object,
}
}
pub fn iri(subject: &str, predicate: &str, object: &str) -> Self {
Self {
subject: RdfTerm::Iri(subject.to_string()),
predicate: predicate.to_string(),
object: RdfTerm::Iri(object.to_string()),
}
}
pub fn literal(subject: &str, predicate: &str, value: &str, datatype: Option<&str>) -> Self {
Self {
subject: RdfTerm::Iri(subject.to_string()),
predicate: predicate.to_string(),
object: RdfTerm::Literal {
value: value.to_string(),
datatype: datatype.map(String::from),
language: None,
},
}
}
}
impl SignedGraph {
pub fn new(graph_uri: &str, triples: Vec<RdfTriple>, issuer: Did) -> Self {
Self {
graph_uri: graph_uri.to_string(),
triples,
issuer: issuer.clone(),
issued_at: Utc::now(),
proof: Proof::ed25519(
&issuer.key_id("key-1"),
ProofPurpose::AssertionMethod,
&[], ),
expires_at: None,
}
}
pub fn sign(mut self, signer: &crate::proof::ed25519::Ed25519Signer) -> DidResult<Self> {
let canonical = self.canonicalize()?;
use sha2::{Digest, Sha256};
let hash = Sha256::digest(canonical.as_bytes());
let signature = signer.sign(&hash);
self.proof = Proof::ed25519(
&self.issuer.key_id("key-1"),
ProofPurpose::AssertionMethod,
&signature,
);
Ok(self)
}
pub async fn verify(
&self,
resolver: &crate::DidResolver,
) -> DidResult<crate::VerificationResult> {
let did_doc = resolver.resolve(&self.issuer).await?;
let vm = did_doc.get_assertion_method().ok_or_else(|| {
crate::DidError::VerificationFailed("No assertion method in DID Document".to_string())
})?;
let public_key = vm.get_public_key_bytes()?;
let canonical = self.canonicalize()?;
use sha2::{Digest, Sha256};
let hash = Sha256::digest(canonical.as_bytes());
let signature = self.proof.get_signature_bytes()?;
let verifier = crate::proof::ed25519::Ed25519Verifier::from_bytes(&public_key)?;
let valid = verifier.verify(&hash, &signature)?;
if valid {
Ok(crate::VerificationResult::success(self.issuer.as_str())
.with_check("signature", true, None)
.with_check("expiration", !self.is_expired(), None))
} else {
Ok(crate::VerificationResult::failure("Invalid signature"))
}
}
fn canonicalize(&self) -> DidResult<String> {
canonicalization::canonicalize_graph(&self.triples)
}
pub fn is_expired(&self) -> bool {
if let Some(expires) = self.expires_at {
Utc::now() > expires
} else {
false
}
}
pub fn with_expiration(mut self, expires: DateTime<Utc>) -> Self {
self.expires_at = Some(expires);
self
}
pub fn hash(&self) -> DidResult<String> {
let canonical = self.canonicalize()?;
use sha2::{Digest, Sha256};
let hash = Sha256::digest(canonical.as_bytes());
Ok(hex::encode(hash))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proof::ed25519::Ed25519Signer;
#[test]
fn test_rdf_triple() {
let triple = RdfTriple::iri(
"http://example.org/subject",
"http://example.org/predicate",
"http://example.org/object",
);
assert!(matches!(triple.subject, RdfTerm::Iri(_)));
assert!(matches!(triple.object, RdfTerm::Iri(_)));
}
#[test]
fn test_signed_graph() {
let signer = Ed25519Signer::generate();
let public_key = signer.public_key_bytes();
let issuer = Did::new_key_ed25519(&public_key).unwrap();
let triples = vec![RdfTriple::iri(
"http://example.org/s",
"http://example.org/p",
"http://example.org/o",
)];
let graph = SignedGraph::new("http://example.org/graph", triples, issuer);
let signed = graph.sign(&signer).unwrap();
assert!(signed.proof.proof_value.is_some());
}
}