pub mod ed25519;
pub mod jws;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProofType {
#[serde(rename = "Ed25519Signature2020")]
Ed25519Signature2020,
#[serde(rename = "DataIntegrityProof")]
DataIntegrityProof,
#[serde(rename = "JsonWebSignature2020")]
JsonWebSignature2020,
}
impl ProofType {
pub fn as_str(&self) -> &'static str {
match self {
ProofType::Ed25519Signature2020 => "Ed25519Signature2020",
ProofType::DataIntegrityProof => "DataIntegrityProof",
ProofType::JsonWebSignature2020 => "JsonWebSignature2020",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ProofPurpose {
AssertionMethod,
Authentication,
KeyAgreement,
CapabilityInvocation,
CapabilityDelegation,
}
impl ProofPurpose {
pub fn as_str(&self) -> &'static str {
match self {
ProofPurpose::AssertionMethod => "assertionMethod",
ProofPurpose::Authentication => "authentication",
ProofPurpose::KeyAgreement => "keyAgreement",
ProofPurpose::CapabilityInvocation => "capabilityInvocation",
ProofPurpose::CapabilityDelegation => "capabilityDelegation",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Proof {
#[serde(rename = "type")]
pub proof_type: String,
pub created: DateTime<Utc>,
pub verification_method: String,
pub proof_purpose: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof_value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jws: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub challenge: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cryptosuite: Option<String>,
}
impl Proof {
pub fn ed25519(verification_method: &str, purpose: ProofPurpose, signature: &[u8]) -> Self {
let proof_value = format!("z{}", bs58::encode(signature).into_string());
Self {
proof_type: ProofType::Ed25519Signature2020.as_str().to_string(),
created: Utc::now(),
verification_method: verification_method.to_string(),
proof_purpose: purpose.as_str().to_string(),
proof_value: Some(proof_value),
jws: None,
challenge: None,
domain: None,
nonce: None,
cryptosuite: None,
}
}
pub fn data_integrity(
cryptosuite: &str,
verification_method: &str,
purpose: ProofPurpose,
signature: &[u8],
) -> Self {
let proof_value = format!("z{}", bs58::encode(signature).into_string());
Self {
proof_type: ProofType::DataIntegrityProof.as_str().to_string(),
created: Utc::now(),
verification_method: verification_method.to_string(),
proof_purpose: purpose.as_str().to_string(),
proof_value: Some(proof_value),
jws: None,
challenge: None,
domain: None,
nonce: None,
cryptosuite: Some(cryptosuite.to_string()),
}
}
pub fn get_signature_bytes(&self) -> crate::DidResult<Vec<u8>> {
if let Some(ref proof_value) = self.proof_value {
if let Some(stripped) = proof_value.strip_prefix('z') {
bs58::decode(stripped)
.into_vec()
.map_err(|e| crate::DidError::InvalidProof(e.to_string()))
} else {
Err(crate::DidError::InvalidProof(
"Unknown proof value encoding".to_string(),
))
}
} else if let Some(ref _jws) = self.jws {
Err(crate::DidError::InvalidProof(
"JWS parsing not yet implemented".to_string(),
))
} else {
Err(crate::DidError::InvalidProof("No proof value".to_string()))
}
}
pub fn with_challenge(mut self, challenge: &str) -> Self {
self.challenge = Some(challenge.to_string());
self
}
pub fn with_domain(mut self, domain: &str) -> Self {
self.domain = Some(domain.to_string());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ed25519_proof() {
let signature = vec![1, 2, 3, 4, 5, 6, 7, 8];
let proof = Proof::ed25519(
"did:key:z123#key-1",
ProofPurpose::AssertionMethod,
&signature,
);
assert_eq!(proof.proof_type, "Ed25519Signature2020");
assert!(proof.proof_value.is_some());
let recovered = proof.get_signature_bytes().unwrap();
assert_eq!(recovered, signature);
}
#[test]
fn test_data_integrity_proof() {
let signature = vec![1, 2, 3, 4];
let proof = Proof::data_integrity(
"eddsa-rdfc-2022",
"did:key:z456#key-1",
ProofPurpose::Authentication,
&signature,
);
assert_eq!(proof.proof_type, "DataIntegrityProof");
assert_eq!(proof.cryptosuite, Some("eddsa-rdfc-2022".to_string()));
}
}