Skip to main content

delano_wallet_core/
lib.rs

1//! Delanocreds wallet code
2#![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
16/// Delano Wallet
17pub struct DelanoWallet {
18    nym: Nym<Initial>,
19    issuer: Issuer,
20}
21
22/// Optionally pass in NymProof and.or None when issuing credentials
23#[derive(Serialize, Deserialize)]
24pub struct IssueOptions {
25    pub nym_proof: NymProofCompressed,
26    pub nonce: Option<Vec<u8>>,
27}
28
29/// Specifies what [Entry]s to include, and which [Attribute]s to exclude from the cred.
30///
31/// # Note on Removing Attributes:
32///
33/// The [Credential] Offer Builder will remove the entire [Entry] containing an [Attribute] to
34/// be removed. If you want to keep other [Attribute]s contained in that [Entry], you'll need
35/// to add those [Attribute]s as an additional [Entry] using [OfferConfig].
36#[derive(Serialize, Deserialize)]
37pub struct Selectables {
38    pub entries: Vec<Entry>,
39    pub remove: Vec<Attribute>,
40}
41
42/// Configure the Offer: Optionally pass in [Selectables] and/or an additional [Entry].
43/// Optionally set the maximum number of entries that can be added to the credential.
44#[derive(Serialize, Deserialize)]
45pub struct OfferConfig {
46    pub selected: Option<Selectables>,
47    pub additional_entry: Option<Entry>,
48    /// Optionally reduces the number of entries that can be added to the credential.
49    pub max_entries: Option<u8>,
50}
51
52/// Values needed to be passed to the `prove` function.
53#[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/// Values needed to be passed to the `verify` function.
62#[derive(Serialize, Deserialize)]
63pub struct Proven {
64    pub proof: CredProofCompressed,
65    pub selected: Vec<Entry>,
66}
67
68/// Values needed to be passed to the `verify` function.
69#[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    /// Creates a new wallet from the given seed
79    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    /// Return proof of [Nym] given the Nonce
97    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    /// Issue a credential for the given [Attribute]s, allow extending up to [MaxEntries].
103    /// Optionally pass in [IssueOptions] to specify the audience [NymProof] and [Nonce].
104    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        // serialize and return the cred
136        let cred_compressed = CredentialCompressed::from(&cred);
137        Ok(cred_compressed.into())
138    }
139
140    // / Create an [CredentialCompressed] offer for the given [OfferConfig].
141    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        // Finally, build the offer
175        let (offer, _provable_entries) = offer_builder
176            .open_offer()
177            .map_err(|e| format!("Error building offer: {:?}", e))?;
178
179        // return the serialized Offer bytes & the provable entries
180        // we do not return _provable_entries in order to keep it separated from the offer in order
181        // to prevent both from falling into the wrong hands. Rather, the application should create
182        // some sort of "Hint" object to relay to the accepter of this offer.
183        let offer_compressed = CredentialCompressed::from(offer.as_ref());
184        Ok(offer_compressed.into())
185    }
186
187    /// Accept a [CredentialCompressed] offer and return a [CredentialCompressed] credential.
188    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    /// Extend the given [CredentialCompressed] with the given [Entry].
201    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    /// Prove the given [Provables], and return a [Proven] proof.
218    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    /// Verify the given [Verifiables]
240    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}