baseid_anoncreds/
proof.rs

1//! AnonCreds proof request and response types.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::credential::AnonCredsCredential;
8
9/// An AnonCreds proof request.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ProofRequest {
12    /// Proof request name.
13    pub name: String,
14    /// Proof request version.
15    pub version: String,
16    /// Nonce for replay protection.
17    pub nonce: String,
18    /// Requested attributes (referent -> requested attribute).
19    pub requested_attributes: BTreeMap<String, RequestedAttribute>,
20    /// Requested predicates (referent -> requested predicate).
21    pub requested_predicates: BTreeMap<String, RequestedPredicate>,
22}
23
24/// A requested attribute in a proof request.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct RequestedAttribute {
27    /// Single attribute name (mutually exclusive with `names`).
28    pub name: Option<String>,
29    /// Group of attribute names to be revealed together.
30    pub names: Option<Vec<String>>,
31    /// Optional restriction filters.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub restrictions: Option<Vec<serde_json::Value>>,
34}
35
36/// A requested predicate in a proof request.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct RequestedPredicate {
39    /// Attribute name for the predicate.
40    pub name: String,
41    /// Predicate type (`>=`, `<=`, `>`, `<`).
42    pub p_type: String,
43    /// Predicate threshold value.
44    pub p_value: i64,
45}
46
47impl ProofRequest {
48    /// Check whether a credential can satisfy this proof request.
49    ///
50    /// Returns `true` if the credential contains all requested attribute names
51    /// and all requested predicate attribute names.
52    pub fn matches_credential(&self, credential: &AnonCredsCredential) -> bool {
53        // Check all requested attributes are present in the credential.
54        for attr in self.requested_attributes.values() {
55            if let Some(ref name) = attr.name {
56                if !credential.values.contains_key(name) {
57                    return false;
58                }
59            }
60            if let Some(ref names) = attr.names {
61                for name in names {
62                    if !credential.values.contains_key(name) {
63                        return false;
64                    }
65                }
66            }
67        }
68
69        // Check all requested predicate attributes are present.
70        for pred in self.requested_predicates.values() {
71            if !credential.values.contains_key(&pred.name) {
72                return false;
73            }
74        }
75
76        true
77    }
78}
79
80/// A proof response containing revealed attributes and self-attested values.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct ProofResponse {
83    /// Revealed attributes (referent -> revealed attribute).
84    pub revealed_attrs: BTreeMap<String, RevealedAttribute>,
85    /// Self-attested attributes (referent -> value).
86    pub self_attested_attrs: BTreeMap<String, String>,
87}
88
89/// A revealed attribute in a proof response.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct RevealedAttribute {
92    /// Index into the list of sub-proofs.
93    pub sub_proof_index: u32,
94    /// Raw (human-readable) attribute value.
95    pub raw: String,
96    /// Integer-encoded attribute value.
97    pub encoded: String,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::credential::{AnonCredsCredential, AttributeValue};
104
105    fn sample_credential() -> AnonCredsCredential {
106        let mut values = BTreeMap::new();
107        values.insert(
108            "name".to_string(),
109            AttributeValue {
110                raw: "Alice".to_string(),
111                encoded: "123".to_string(),
112            },
113        );
114        values.insert(
115            "age".to_string(),
116            AttributeValue {
117                raw: "30".to_string(),
118                encoded: "30".to_string(),
119            },
120        );
121        values.insert(
122            "degree".to_string(),
123            AttributeValue {
124                raw: "CS".to_string(),
125                encoded: "456".to_string(),
126            },
127        );
128        AnonCredsCredential {
129            schema_id: "schema:1".to_string(),
130            cred_def_id: "cred_def:1".to_string(),
131            rev_reg_id: None,
132            issuer_did: "did:sov:issuer".to_string(),
133            values,
134        }
135    }
136
137    #[test]
138    fn proof_request_matches_credential() {
139        let mut requested_attributes = BTreeMap::new();
140        requested_attributes.insert(
141            "attr1_referent".to_string(),
142            RequestedAttribute {
143                name: Some("name".to_string()),
144                names: None,
145                restrictions: None,
146            },
147        );
148
149        let request = ProofRequest {
150            name: "proof-req".to_string(),
151            version: "1.0".to_string(),
152            nonce: "12345".to_string(),
153            requested_attributes,
154            requested_predicates: BTreeMap::new(),
155        };
156
157        let cred = sample_credential();
158        assert!(request.matches_credential(&cred));
159    }
160
161    #[test]
162    fn proof_request_does_not_match_missing_attr() {
163        let mut requested_attributes = BTreeMap::new();
164        requested_attributes.insert(
165            "attr1_referent".to_string(),
166            RequestedAttribute {
167                name: Some("missing_field".to_string()),
168                names: None,
169                restrictions: None,
170            },
171        );
172
173        let request = ProofRequest {
174            name: "proof-req".to_string(),
175            version: "1.0".to_string(),
176            nonce: "99999".to_string(),
177            requested_attributes,
178            requested_predicates: BTreeMap::new(),
179        };
180
181        let cred = sample_credential();
182        assert!(!request.matches_credential(&cred));
183    }
184
185    #[test]
186    fn proof_request_matches_group_names() {
187        let mut requested_attributes = BTreeMap::new();
188        requested_attributes.insert(
189            "group_referent".to_string(),
190            RequestedAttribute {
191                name: None,
192                names: Some(vec!["name".to_string(), "degree".to_string()]),
193                restrictions: None,
194            },
195        );
196
197        let request = ProofRequest {
198            name: "proof-req".to_string(),
199            version: "1.0".to_string(),
200            nonce: "55555".to_string(),
201            requested_attributes,
202            requested_predicates: BTreeMap::new(),
203        };
204
205        let cred = sample_credential();
206        assert!(request.matches_credential(&cred));
207    }
208
209    #[test]
210    fn proof_response_create() {
211        let mut revealed = BTreeMap::new();
212        revealed.insert(
213            "attr1_referent".to_string(),
214            RevealedAttribute {
215                sub_proof_index: 0,
216                raw: "Alice".to_string(),
217                encoded: "123".to_string(),
218            },
219        );
220
221        let response = ProofResponse {
222            revealed_attrs: revealed,
223            self_attested_attrs: BTreeMap::new(),
224        };
225
226        assert_eq!(response.revealed_attrs.len(), 1);
227        assert_eq!(response.revealed_attrs["attr1_referent"].raw, "Alice");
228    }
229}