1pub mod ed25519;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ProofType {
11 #[serde(rename = "Ed25519Signature2020")]
13 Ed25519Signature2020,
14 #[serde(rename = "DataIntegrityProof")]
16 DataIntegrityProof,
17 #[serde(rename = "JsonWebSignature2020")]
19 JsonWebSignature2020,
20}
21
22impl ProofType {
23 pub fn as_str(&self) -> &'static str {
24 match self {
25 ProofType::Ed25519Signature2020 => "Ed25519Signature2020",
26 ProofType::DataIntegrityProof => "DataIntegrityProof",
27 ProofType::JsonWebSignature2020 => "JsonWebSignature2020",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub enum ProofPurpose {
36 AssertionMethod,
38 Authentication,
40 KeyAgreement,
42 CapabilityInvocation,
44 CapabilityDelegation,
46}
47
48impl ProofPurpose {
49 pub fn as_str(&self) -> &'static str {
50 match self {
51 ProofPurpose::AssertionMethod => "assertionMethod",
52 ProofPurpose::Authentication => "authentication",
53 ProofPurpose::KeyAgreement => "keyAgreement",
54 ProofPurpose::CapabilityInvocation => "capabilityInvocation",
55 ProofPurpose::CapabilityDelegation => "capabilityDelegation",
56 }
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct Proof {
64 #[serde(rename = "type")]
66 pub proof_type: String,
67
68 pub created: DateTime<Utc>,
70
71 pub verification_method: String,
73
74 pub proof_purpose: String,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub proof_value: Option<String>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub jws: Option<String>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub challenge: Option<String>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub domain: Option<String>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub nonce: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub cryptosuite: Option<String>,
100}
101
102impl Proof {
103 pub fn ed25519(verification_method: &str, purpose: ProofPurpose, signature: &[u8]) -> Self {
105 let proof_value = format!("z{}", bs58::encode(signature).into_string());
107
108 Self {
109 proof_type: ProofType::Ed25519Signature2020.as_str().to_string(),
110 created: Utc::now(),
111 verification_method: verification_method.to_string(),
112 proof_purpose: purpose.as_str().to_string(),
113 proof_value: Some(proof_value),
114 jws: None,
115 challenge: None,
116 domain: None,
117 nonce: None,
118 cryptosuite: None,
119 }
120 }
121
122 pub fn data_integrity(
124 cryptosuite: &str,
125 verification_method: &str,
126 purpose: ProofPurpose,
127 signature: &[u8],
128 ) -> Self {
129 let proof_value = format!("z{}", bs58::encode(signature).into_string());
130
131 Self {
132 proof_type: ProofType::DataIntegrityProof.as_str().to_string(),
133 created: Utc::now(),
134 verification_method: verification_method.to_string(),
135 proof_purpose: purpose.as_str().to_string(),
136 proof_value: Some(proof_value),
137 jws: None,
138 challenge: None,
139 domain: None,
140 nonce: None,
141 cryptosuite: Some(cryptosuite.to_string()),
142 }
143 }
144
145 pub fn get_signature_bytes(&self) -> crate::DidResult<Vec<u8>> {
147 if let Some(ref proof_value) = self.proof_value {
148 if let Some(stripped) = proof_value.strip_prefix('z') {
149 bs58::decode(stripped)
150 .into_vec()
151 .map_err(|e| crate::DidError::InvalidProof(e.to_string()))
152 } else {
153 Err(crate::DidError::InvalidProof(
154 "Unknown proof value encoding".to_string(),
155 ))
156 }
157 } else if let Some(ref _jws) = self.jws {
158 Err(crate::DidError::InvalidProof(
160 "JWS parsing not yet implemented".to_string(),
161 ))
162 } else {
163 Err(crate::DidError::InvalidProof("No proof value".to_string()))
164 }
165 }
166
167 pub fn with_challenge(mut self, challenge: &str) -> Self {
169 self.challenge = Some(challenge.to_string());
170 self
171 }
172
173 pub fn with_domain(mut self, domain: &str) -> Self {
175 self.domain = Some(domain.to_string());
176 self
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_ed25519_proof() {
186 let signature = vec![1, 2, 3, 4, 5, 6, 7, 8];
187 let proof = Proof::ed25519(
188 "did:key:z123#key-1",
189 ProofPurpose::AssertionMethod,
190 &signature,
191 );
192
193 assert_eq!(proof.proof_type, "Ed25519Signature2020");
194 assert!(proof.proof_value.is_some());
195
196 let recovered = proof.get_signature_bytes().unwrap();
197 assert_eq!(recovered, signature);
198 }
199
200 #[test]
201 fn test_data_integrity_proof() {
202 let signature = vec![1, 2, 3, 4];
203 let proof = Proof::data_integrity(
204 "eddsa-rdfc-2022",
205 "did:key:z456#key-1",
206 ProofPurpose::Authentication,
207 &signature,
208 );
209
210 assert_eq!(proof.proof_type, "DataIntegrityProof");
211 assert_eq!(proof.cryptosuite, Some("eddsa-rdfc-2022".to_string()));
212 }
213}