1pub mod canonicalization;
4pub mod signature;
5
6use crate::{Did, DidResult, Proof, ProofPurpose};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct SignedGraph {
14 pub graph_uri: String,
16
17 pub triples: Vec<RdfTriple>,
19
20 pub issuer: Did,
22
23 pub issued_at: DateTime<Utc>,
25
26 pub proof: Proof,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub expires_at: Option<DateTime<Utc>>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub struct RdfTriple {
37 pub subject: RdfTerm,
38 pub predicate: String,
39 pub object: RdfTerm,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[serde(tag = "type", content = "value")]
45pub enum RdfTerm {
46 Iri(String),
48 BlankNode(String),
50 Literal {
52 value: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 datatype: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 language: Option<String>,
57 },
58}
59
60impl RdfTriple {
61 pub fn new(subject: RdfTerm, predicate: &str, object: RdfTerm) -> Self {
62 Self {
63 subject,
64 predicate: predicate.to_string(),
65 object,
66 }
67 }
68
69 pub fn iri(subject: &str, predicate: &str, object: &str) -> Self {
71 Self {
72 subject: RdfTerm::Iri(subject.to_string()),
73 predicate: predicate.to_string(),
74 object: RdfTerm::Iri(object.to_string()),
75 }
76 }
77
78 pub fn literal(subject: &str, predicate: &str, value: &str, datatype: Option<&str>) -> Self {
80 Self {
81 subject: RdfTerm::Iri(subject.to_string()),
82 predicate: predicate.to_string(),
83 object: RdfTerm::Literal {
84 value: value.to_string(),
85 datatype: datatype.map(String::from),
86 language: None,
87 },
88 }
89 }
90}
91
92impl SignedGraph {
93 pub fn new(graph_uri: &str, triples: Vec<RdfTriple>, issuer: Did) -> Self {
95 Self {
96 graph_uri: graph_uri.to_string(),
97 triples,
98 issuer: issuer.clone(),
99 issued_at: Utc::now(),
100 proof: Proof::ed25519(
101 &issuer.key_id("key-1"),
102 ProofPurpose::AssertionMethod,
103 &[], ),
105 expires_at: None,
106 }
107 }
108
109 pub fn sign(mut self, signer: &crate::proof::ed25519::Ed25519Signer) -> DidResult<Self> {
111 let canonical = self.canonicalize()?;
113
114 use sha2::{Digest, Sha256};
116 let hash = Sha256::digest(canonical.as_bytes());
117
118 let signature = signer.sign(&hash);
120
121 self.proof = Proof::ed25519(
123 &self.issuer.key_id("key-1"),
124 ProofPurpose::AssertionMethod,
125 &signature,
126 );
127
128 Ok(self)
129 }
130
131 pub async fn verify(
133 &self,
134 resolver: &crate::DidResolver,
135 ) -> DidResult<crate::VerificationResult> {
136 let did_doc = resolver.resolve(&self.issuer).await?;
138
139 let vm = did_doc.get_assertion_method().ok_or_else(|| {
141 crate::DidError::VerificationFailed("No assertion method in DID Document".to_string())
142 })?;
143
144 let public_key = vm.get_public_key_bytes()?;
145
146 let canonical = self.canonicalize()?;
148
149 use sha2::{Digest, Sha256};
151 let hash = Sha256::digest(canonical.as_bytes());
152
153 let signature = self.proof.get_signature_bytes()?;
155 let verifier = crate::proof::ed25519::Ed25519Verifier::from_bytes(&public_key)?;
156 let valid = verifier.verify(&hash, &signature)?;
157
158 if valid {
159 Ok(crate::VerificationResult::success(self.issuer.as_str())
160 .with_check("signature", true, None)
161 .with_check("expiration", !self.is_expired(), None))
162 } else {
163 Ok(crate::VerificationResult::failure("Invalid signature"))
164 }
165 }
166
167 fn canonicalize(&self) -> DidResult<String> {
169 canonicalization::canonicalize_graph(&self.triples)
170 }
171
172 pub fn is_expired(&self) -> bool {
174 if let Some(expires) = self.expires_at {
175 Utc::now() > expires
176 } else {
177 false
178 }
179 }
180
181 pub fn with_expiration(mut self, expires: DateTime<Utc>) -> Self {
183 self.expires_at = Some(expires);
184 self
185 }
186
187 pub fn hash(&self) -> DidResult<String> {
189 let canonical = self.canonicalize()?;
190 use sha2::{Digest, Sha256};
191 let hash = Sha256::digest(canonical.as_bytes());
192 Ok(hex::encode(hash))
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::proof::ed25519::Ed25519Signer;
200
201 #[test]
202 fn test_rdf_triple() {
203 let triple = RdfTriple::iri(
204 "http://example.org/subject",
205 "http://example.org/predicate",
206 "http://example.org/object",
207 );
208
209 assert!(matches!(triple.subject, RdfTerm::Iri(_)));
210 assert!(matches!(triple.object, RdfTerm::Iri(_)));
211 }
212
213 #[test]
214 fn test_signed_graph() {
215 let signer = Ed25519Signer::generate();
216 let public_key = signer.public_key_bytes();
217 let issuer = Did::new_key_ed25519(&public_key).unwrap();
218
219 let triples = vec![RdfTriple::iri(
220 "http://example.org/s",
221 "http://example.org/p",
222 "http://example.org/o",
223 )];
224
225 let graph = SignedGraph::new("http://example.org/graph", triples, issuer);
226 let signed = graph.sign(&signer).unwrap();
227
228 assert!(signed.proof.proof_value.is_some());
229 }
230}