did_utils/proof/
eddsa_jcs_2022.rs1use multibase::Base;
2
3use crate::crypto::{sha256_hash, CoreSign, Ed25519KeyPair, Error};
4
5use super::{model::Proof, traits::CryptoProof};
6
7pub const CRYPRO_SUITE_EDDSA_JCS_2022: &str = "eddsa-jcs-2022";
8pub const PROOF_TYPE_DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";
9
10pub struct EdDsaJcs2022 {
11 pub proof: Proof,
19
20 pub key_pair: Ed25519KeyPair,
27
28 pub proof_value_codec: Option<Base>,
32}
33
34impl CryptoProof for EdDsaJcs2022 {
35 fn proof(&self, payload: serde_json::Value) -> Result<Proof, Error> {
36 match self.proof_value_codec {
37 None => Err(Error::InvalidCall("proof_value_codec must be set for proof creation".to_string())),
38 Some(_) => {
39 let normalized_proof = Proof {
40 proof_type: PROOF_TYPE_DATA_INTEGRITY_PROOF.to_string(),
41 cryptosuite: Some(CRYPRO_SUITE_EDDSA_JCS_2022.to_string()),
42 created: match self.proof.created {
43 Some(created) => Some(created),
44 None => Some(chrono::Utc::now()),
45 },
46 proof_value: None,
47 ..self.proof.clone()
48 };
49
50 let canon_proof = json_canon::to_string(&normalized_proof).map_err(|_| Error::InvalidProof)?;
52 let canon_doc = json_canon::to_string(&payload).map_err(|_| Error::InvalidProof)?;
53
54 let hash = [sha256_hash(canon_proof.as_bytes()), sha256_hash(canon_doc.as_bytes())].concat();
56
57 self.key_pair.sign(&hash[..]).map(|signature| Proof {
58 proof_value: Some(multibase::encode(self.proof_value_codec.unwrap(), signature)),
59 ..normalized_proof
60 })
61 }
62 }
63 }
64
65 fn verify(&self, payload: serde_json::Value) -> Result<(), Error> {
66 match self.proof.proof_value.clone() {
67 None => Err(Error::InvalidProof),
68 Some(proof_value) => {
69 let normalized_proof = Proof {
74 proof_value: None,
75 proof_type: PROOF_TYPE_DATA_INTEGRITY_PROOF.to_string(),
76 cryptosuite: Some(CRYPRO_SUITE_EDDSA_JCS_2022.to_string()),
77 ..self.proof.clone()
78 };
79
80 let naked_payload = match payload.get("proof") {
82 None => payload,
83 Some(_) => {
84 let mut naked_payload = payload.clone();
85 naked_payload.as_object_mut().unwrap().remove("proof");
86 naked_payload
87 }
88 };
89
90 let canon_proof = json_canon::to_string(&normalized_proof).map_err(|_| Error::InvalidProof)?;
92 let canon_doc = json_canon::to_string(&naked_payload).map_err(|_| Error::InvalidProof)?;
93
94 let hash = [sha256_hash(canon_proof.as_bytes()), sha256_hash(canon_doc.as_bytes())].concat();
96
97 multibase::decode(proof_value)
98 .map_err(|_| Error::InvalidProof)
99 .and_then(|signature| self.key_pair.verify(&hash, &(signature.1)))
100 }
101 }
102 }
103}
104
105#[cfg(test)]
107mod tests {
108 use serde_json::Value;
109
110 use crate::{crypto::Generate, proof::model::UnsecuredDocument};
111
112 #[test]
115 fn test_create_verify_proof() {
116 use chrono::TimeZone;
117 use serde_json::json;
118
119 use crate::crypto::Ed25519KeyPair;
120
121 use super::*;
122
123 let my_string = String::from("Sample seed bytes of thirtytwo!b");
124 let seed: &[u8] = my_string.as_bytes();
125 let key_pair = Ed25519KeyPair::new_with_seed(seed).unwrap();
126 let public_key = &key_pair.public_key.clone();
127
128 let proof = Proof {
129 id: None,
130 proof_type: "DataIntegrityProof".to_string(),
131 cryptosuite: Some("jcs-eddsa-2022".to_string()),
132 proof_purpose: "assertionMethod".to_string(),
133 verification_method: "https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG".to_string(),
134 created: Some(chrono::Utc.with_ymd_and_hms(2023, 3, 5, 19, 23, 24).unwrap()),
135 expires: None,
136 domain: Some(crate::proof::model::Domain::SingleString("vc-demo.adorsys.com".to_string())),
137 challenge: Some("523452345234asfdasdfasdfa".to_string()),
138 proof_value: None,
139 previous_proof: None,
140 nonce: Some("1234567890".to_string()),
141 };
142
143 let payload = json!({
144 "id": "did:example:123456789abcdefghi",
145 "name": "Alice",
146 "age": 101,
147 "image": "data:image/png;base64,iVBORw0KGgo...kJggg==",
148 });
149
150 let ed_dsa_jcs_2022_prover = EdDsaJcs2022 {
151 proof,
152 key_pair,
153 proof_value_codec: Some(Base::Base58Btc),
154 };
155
156 let secured_proof = ed_dsa_jcs_2022_prover.proof(payload.clone()).unwrap();
157
158 let secure_doc = UnsecuredDocument {
159 content: payload,
160 proof: crate::proof::model::Proofs::SingleProof(Box::new(secured_proof.clone())),
161 };
162
163 let expected_canonicalized_proof = r#"{"challenge":"523452345234asfdasdfasdfa","created":"2023-03-05T19:23:24Z","cryptosuite":"eddsa-jcs-2022","domain":"vc-demo.adorsys.com","nonce":"1234567890","proofPurpose":"assertionMethod","proofValue":"z2DbDNkE47SquDQ7wM6p3RjNdFB1FG7Num2w9kprZjUB2gNZvz7bYgcT5XCe3TdjfxxWfKkup1ZdrRhfEMLsk2kmr","type":"DataIntegrityProof","verificationMethod":"https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG"}"#;
164 let canonicalized_proof = json_canon::to_string(&secured_proof).unwrap();
165 assert_eq!(expected_canonicalized_proof, canonicalized_proof);
166
167 let secure_doc_json_value: Value = serde_json::to_value(&secure_doc).expect("Failed to serialize");
170
171 let ed_dsa_jcs_2022_verifier = EdDsaJcs2022 {
172 proof: secured_proof,
173 key_pair: Ed25519KeyPair::from_public_key(public_key.as_bytes()).unwrap(),
174 proof_value_codec: None,
175 };
176
177 ed_dsa_jcs_2022_verifier.verify(secure_doc_json_value).unwrap();
178 }
179}