Skip to content

Commit

Permalink
scaffold delano-wallet-core crate
Browse files Browse the repository at this point in the history
  • Loading branch information
DougAnderson444 committed Jul 6, 2024
1 parent cff197d commit a0b9f25
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 0 deletions.
13 changes: 13 additions & 0 deletions crates/delano-wallet-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "delano-wallet-core"
edition = "2021"
version.workspace = true
authors.workspace = true
description.workspace = true
repository.workspace = true

[dependencies]
delanocreds = { version = "0.2.0", workspace = true }
delano-keys = { version = "0.1.1", workspace = true }
serde = { version = "1.0", features = ["derive"] }
zeroize = "1.6"
3 changes: 3 additions & 0 deletions crates/delano-wallet-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Delanocreds Wallet Core

A crate which provides the core functionality for the Delanocreds wallet. This can then be wrapped by other utilities such as `wasm-bindgen`, etc.
261 changes: 261 additions & 0 deletions crates/delano-wallet-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
//! Delanocreds wallet code
#![doc = include_str!("../README.md")]

use delano_keys::kdf::{ExposeSecret, Manager};
use delano_keys::{
kdf::Scalar,
// vk::VKCompressed
};
use delanocreds::keypair::{CredProofCompressed, IssuerPublicCompressed, NymProofCompressed};
use delanocreds::{
verify_proof, CredProof, Credential, Entry, Initial, Issuer, IssuerPublic, MaxCardinality,
MaxEntries, Nonce, Nym, NymProof, Secret,
};

use delanocreds::utils;
use delanocreds::{Attribute, CredentialCompressed};
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};

/// Delano Wallet
pub struct DelanoWallet {
expanded: Secret<Vec<Scalar>>,
nym: Nym<Initial>,
issuer: Issuer,
}

/// Optionally pass in NymProof and.or None when issuing credentials
#[derive(Serialize, Deserialize)]
pub struct IssueOptions {
pub nym_proof: NymProofCompressed,
pub nonce: Option<Vec<u8>>,
}

/// Specifies what [Entry]s to include, and which [Attribute]s to exclude from the cred.
///
/// # Note on Removing Attributes:
///
/// The [Credential] Offer Builder will remove the entire [Entry] containing an [Attribute] to
/// be removed. If you want to keep other [Attribute]s contained in that [Entry], you'll need
/// to add those [Attribute]s as an additional [Entry] using [OfferConfig].
#[derive(Serialize, Deserialize)]
pub struct Selectables {
pub entries: Vec<Entry>,
pub remove: Vec<Attribute>,
}

#[derive(Serialize, Deserialize)]
pub struct OfferConfig {
pub selected: Option<Selectables>,
pub additional_entry: Option<Entry>,
/// Optionally reduces the number of entries that can be added to the credential.
pub max_entries: Option<u8>,
}

/// Values needed to be passed to the `prove` function
#[derive(Serialize, Deserialize)]
pub struct Provables {
pub credential: CredentialCompressed,
pub entries: Vec<Entry>,
pub selected: Vec<Attribute>,
pub nonce: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
pub struct Proven {
pub proof: CredProofCompressed,
pub selected: Vec<Entry>,
}

#[derive(Serialize, Deserialize)]
pub struct Verifiables {
pub proof: CredProofCompressed,
pub issuer_public: IssuerPublicCompressed,
pub nonce: Option<Vec<u8>>,
pub selected: Vec<Entry>,
}

impl DelanoWallet {
/// Creates a new wallet from the given seed
pub fn new(seed: impl AsRef<[u8]> + Zeroize + ZeroizeOnDrop) -> Self {
let manager = Manager::from_seed(seed);

let account = manager.account(1);

let expanded = account.expand_to(MaxEntries::default().into());

let nym = Nym::from_secret(expanded.expose_secret().clone()[0].into());

let issuer = Issuer::new_with_secret(
expanded.expose_secret().clone().into(),
MaxCardinality::default(),
);

Self {
expanded,
nym,
issuer,
}
}

/// Return proof of [Nym] given the Nonce
pub fn get_nym_proof(&self, nonce: Vec<u8>) -> NymProofCompressed {
let nonce = utils::nonce_by_len(&nonce).unwrap_or_default();
NymProofCompressed::from(self.nym.nym_proof(&nonce))
}

/// Issue a credential for the given [Attribute]s, allow extending up to [MaxEntries].
/// Optionally pass in [IssueOptions] to specify the audience [NymProof] and [Nonce].
pub fn issue(
&self,
attributes: Vec<Attribute>,
max_entries: MaxEntries,
options: Option<IssueOptions>,
) -> Result<CredentialCompressed, String> {
let entry = Entry::try_from(attributes).map_err(|e| e.to_string())?;

let (nym_proof, nonce) = match options {
Some(options) => {
let nonce = utils::maybe_nonce(options.nonce.as_deref().map(|n| n.as_ref()))?;
let nym_proof = NymProof::try_from(NymProofCompressed::from(options.nym_proof))
.map_err(|e| {
format!("Error converting nym_proof bytes into NymProof: {:?}", e)
})?;
(nym_proof, nonce)
}
_ => {
let nonce = utils::nonce_by_len(&[0u8; 32]).map_err(|e| e.to_string())?;
let nym_proof = self.nym.nym_proof(&nonce);
(nym_proof, Some(nonce))
}
};
let cred = self
.issuer
.credential()
.with_entry(entry)
.max_entries(&max_entries.into())
.issue_to(&nym_proof, nonce.as_ref())
.map_err(|e| format!("Error issuing credential: {:?}", e))?;

// serialize and return the cred
let cred_compressed = CredentialCompressed::from(&cred);
Ok(cred_compressed.into())
}

// / Create an [CredentialCompressed] offer for the given [OfferConfig].
pub fn offer(
&self,
cred: CredentialCompressed,
config: OfferConfig,
) -> Result<CredentialCompressed, String> {
let cred = Credential::try_from(cred).map_err(|e| e.to_string())?;

let (entries, redact) = match config.selected {
Some(includables) => {
let entries = includables.entries;
let redact = includables.remove;
(entries, redact)
}
_ => (vec![], vec![]),
};

let mut offer_builder = self.nym.offer_builder(&cred, &entries);

if let Some(additional_entry) = config.additional_entry {
offer_builder.additional_entry(
Entry::try_from(additional_entry)
.map_err(|e| format!("Additional entry failed {}", e.to_string()))?,
);
}

for r in redact {
offer_builder.without_attribute(r);
}

if let Some(max_entries) = config.max_entries {
offer_builder.max_entries(*MaxEntries::new(max_entries.into()));
}

// Finally, build the offer
let (offer, _provable_entries) = offer_builder
.open_offer()
.map_err(|e| format!("Error building offer: {:?}", e))?;

// return the serialized Offer bytes & the provable entries
// we do not return _provable_entries in order to keep it separated from the offer in order
// to prevent both from falling into the wrong hands. Rather, the application should create
// some sort of "Hint" object to relay to the accepter of this offer.
let offer_compressed = CredentialCompressed::from(offer.as_ref());
Ok(offer_compressed.into())
}

/// Accept a [CredentialCompressed] offer and return a [CredentialCompressed] credential.
pub fn accept(&self, offer: CredentialCompressed) -> Result<CredentialCompressed, String> {
let offer = Credential::try_from(offer).map_err(|e| e.to_string())?;

let accepted_cred = self
.nym
.accept(&offer.into())
.map_err(|e| format!("Error accepting offer: {:?}", e))?;

let accepted_compressed = CredentialCompressed::from(&accepted_cred);
Ok(accepted_compressed.into())
}

/// Extend the given [CredentialCompressed] with the given [Entry].
pub fn extend(
&self,
cred: CredentialCompressed,
entry: Entry,
) -> Result<CredentialCompressed, String> {
let cred = Credential::try_from(cred).map_err(|e| e.to_string())?;

let extended_cred = self
.nym
.extend(&cred, &entry)
.map_err(|e| format!("Error extending credential: {:?}", e))?;

let extended_compressed = CredentialCompressed::from(&extended_cred);
Ok(extended_compressed.into())
}

/// Prove the given [Provables], and return a [Proven] proof.
pub fn prove(&self, provables: Provables) -> Result<Proven, String> {
let cred = Credential::try_from(provables.credential).map_err(|e| e.to_string())?;

let mut buildr = self.nym.proof_builder(&cred, &provables.entries);

for s in provables.selected {
buildr.select_attribute(s);
}

let nonce: Nonce = utils::nonce_by_len(&provables.nonce)?;

let (proof, selected_entries) = buildr.prove(&nonce);

let proof_compressed = CredProofCompressed::from(proof);

Ok(Proven {
proof: proof_compressed,
selected: selected_entries,
})
}

/// Verify the given [Verifiables]
pub fn verify(&self, verifiables: Verifiables) -> Result<bool, String> {
let issuer_public =
IssuerPublic::try_from(verifiables.issuer_public).map_err(|e| e.to_string())?;
let proof = CredProof::try_from(verifiables.proof).map_err(|e| e.to_string())?;

let nonce = utils::maybe_nonce(verifiables.nonce.as_deref())?;

let selected_entries = verifiables.selected;

Ok(verify_proof(
&issuer_public,
&proof,
&selected_entries,
nonce.as_ref(),
))
}
}

0 comments on commit a0b9f25

Please sign in to comment.