1#![doc = include_str!("../README.md")]
3
4use delano_keys::kdf::{ExposeSecret, Manager};
5use delanocreds::keypair::{CredProofCompressed, IssuerPublicCompressed, NymProofCompressed};
6use delanocreds::{
7 verify_proof, CredProof, Credential, Entry, Initial, Issuer, IssuerPublic, MaxCardinality,
8 MaxEntries, Nonce, Nym, NymProof,
9};
10
11use delanocreds::utils;
12use delanocreds::{Attribute, CredentialCompressed};
13use serde::{Deserialize, Serialize};
14use zeroize::{Zeroize, ZeroizeOnDrop};
15
16pub struct DelanoWallet {
18 nym: Nym<Initial>,
19 issuer: Issuer,
20}
21
22#[derive(Serialize, Deserialize)]
24pub struct IssueOptions {
25 pub nym_proof: NymProofCompressed,
26 pub nonce: Option<Vec<u8>>,
27}
28
29#[derive(Serialize, Deserialize)]
37pub struct Selectables {
38 pub entries: Vec<Entry>,
39 pub remove: Vec<Attribute>,
40}
41
42#[derive(Serialize, Deserialize)]
45pub struct OfferConfig {
46 pub selected: Option<Selectables>,
47 pub additional_entry: Option<Entry>,
48 pub max_entries: Option<u8>,
50}
51
52#[derive(Serialize, Deserialize)]
54pub struct Provables {
55 pub credential: CredentialCompressed,
56 pub entries: Vec<Entry>,
57 pub selected: Vec<Attribute>,
58 pub nonce: Vec<u8>,
59}
60
61#[derive(Serialize, Deserialize)]
63pub struct Proven {
64 pub proof: CredProofCompressed,
65 pub selected: Vec<Entry>,
66}
67
68#[derive(Serialize, Deserialize)]
70pub struct Verifiables {
71 pub proof: CredProofCompressed,
72 pub issuer_public: IssuerPublicCompressed,
73 pub nonce: Option<Vec<u8>>,
74 pub selected: Vec<Entry>,
75}
76
77impl DelanoWallet {
78 pub fn new(seed: impl AsRef<[u8]> + Zeroize + ZeroizeOnDrop) -> Self {
80 let manager = Manager::from_seed(seed);
81
82 let account = manager.account(1);
83
84 let expanded = account.expand_to(MaxEntries::default().into());
85
86 let nym = Nym::from_secret(expanded.expose_secret().clone()[0].into());
87
88 let issuer = Issuer::new_with_secret(
89 expanded.expose_secret().clone().into(),
90 MaxCardinality::default(),
91 );
92
93 Self { nym, issuer }
94 }
95
96 pub fn nym_proof(&self, nonce: Vec<u8>) -> NymProofCompressed {
98 let nonce = utils::nonce_by_len(&nonce).unwrap_or_default();
99 NymProofCompressed::from(self.nym.nym_proof(&nonce))
100 }
101
102 pub fn issue(
105 &self,
106 attributes: Vec<Attribute>,
107 max_entries: MaxEntries,
108 options: Option<IssueOptions>,
109 ) -> Result<CredentialCompressed, String> {
110 let entry = Entry::try_from(attributes).map_err(|e| e.to_string())?;
111
112 let (nym_proof, nonce) = match options {
113 Some(options) => {
114 let nonce = utils::maybe_nonce(options.nonce.as_deref().map(|n| n.as_ref()))?;
115 let nym_proof = NymProof::try_from(NymProofCompressed::from(options.nym_proof))
116 .map_err(|e| {
117 format!("Error converting nym_proof bytes into NymProof: {:?}", e)
118 })?;
119 (nym_proof, nonce)
120 }
121 _ => {
122 let nonce = utils::nonce_by_len(&[0u8; 32]).map_err(|e| e.to_string())?;
123 let nym_proof = self.nym.nym_proof(&nonce);
124 (nym_proof, Some(nonce))
125 }
126 };
127 let cred = self
128 .issuer
129 .credential()
130 .with_entry(entry)
131 .max_entries(&max_entries.into())
132 .issue_to(&nym_proof, nonce.as_ref())
133 .map_err(|e| format!("Error issuing credential: {:?}", e))?;
134
135 let cred_compressed = CredentialCompressed::from(&cred);
137 Ok(cred_compressed.into())
138 }
139
140 pub fn offer(
142 &self,
143 cred: CredentialCompressed,
144 config: OfferConfig,
145 ) -> Result<CredentialCompressed, String> {
146 let cred = Credential::try_from(cred).map_err(|e| e.to_string())?;
147
148 let (entries, redact) = match config.selected {
149 Some(includables) => {
150 let entries = includables.entries;
151 let redact = includables.remove;
152 (entries, redact)
153 }
154 _ => (vec![], vec![]),
155 };
156
157 let mut offer_builder = self.nym.offer_builder(&cred, &entries);
158
159 if let Some(additional_entry) = config.additional_entry {
160 offer_builder.additional_entry(
161 Entry::try_from(additional_entry)
162 .map_err(|e| format!("Additional entry failed {}", e.to_string()))?,
163 );
164 }
165
166 for r in redact {
167 offer_builder.without_attribute(r);
168 }
169
170 if let Some(max_entries) = config.max_entries {
171 offer_builder.max_entries(*MaxEntries::new(max_entries.into()));
172 }
173
174 let (offer, _provable_entries) = offer_builder
176 .open_offer()
177 .map_err(|e| format!("Error building offer: {:?}", e))?;
178
179 let offer_compressed = CredentialCompressed::from(offer.as_ref());
184 Ok(offer_compressed.into())
185 }
186
187 pub fn accept(&self, offer: CredentialCompressed) -> Result<CredentialCompressed, String> {
189 let offer = Credential::try_from(offer).map_err(|e| e.to_string())?;
190
191 let accepted_cred = self
192 .nym
193 .accept(&offer.into())
194 .map_err(|e| format!("Error accepting offer: {:?}", e))?;
195
196 let accepted_compressed = CredentialCompressed::from(&accepted_cred);
197 Ok(accepted_compressed.into())
198 }
199
200 pub fn extend(
202 &self,
203 cred: CredentialCompressed,
204 entry: Entry,
205 ) -> Result<CredentialCompressed, String> {
206 let cred = Credential::try_from(cred).map_err(|e| e.to_string())?;
207
208 let extended_cred = self
209 .nym
210 .extend(&cred, &entry)
211 .map_err(|e| format!("Error extending credential: {:?}", e))?;
212
213 let extended_compressed = CredentialCompressed::from(&extended_cred);
214 Ok(extended_compressed.into())
215 }
216
217 pub fn prove(&self, provables: Provables) -> Result<Proven, String> {
219 let cred = Credential::try_from(provables.credential).map_err(|e| e.to_string())?;
220
221 let mut buildr = self.nym.proof_builder(&cred, &provables.entries);
222
223 for s in provables.selected {
224 buildr.select_attribute(s);
225 }
226
227 let nonce: Nonce = utils::nonce_by_len(&provables.nonce)?;
228
229 let (proof, selected_entries) = buildr.prove(&nonce);
230
231 let proof_compressed = CredProofCompressed::from(proof);
232
233 Ok(Proven {
234 proof: proof_compressed,
235 selected: selected_entries,
236 })
237 }
238
239 pub fn verify(&self, verifiables: Verifiables) -> Result<bool, String> {
241 let issuer_public =
242 IssuerPublic::try_from(verifiables.issuer_public).map_err(|e| e.to_string())?;
243 let proof = CredProof::try_from(verifiables.proof).map_err(|e| e.to_string())?;
244
245 let nonce = utils::maybe_nonce(verifiables.nonce.as_deref())?;
246
247 let selected_entries = verifiables.selected;
248
249 Ok(verify_proof(
250 &issuer_public,
251 &proof,
252 &selected_entries,
253 nonce.as_ref(),
254 ))
255 }
256}