#![feature(cfg_eval)]
#![doc = include_str!("../README.md")]
mod attributes;
mod config;
mod ec;
mod entry;
pub mod utils;
pub mod error;
pub mod keypair;
pub mod set_commits;
pub mod zkp;
use anyhow::Result;
pub use attributes::Attribute;
use bls12_381_plus::Scalar;
pub use entry::Entry;
pub use entry::MaxEntries;
pub use keypair::NymProof;
pub use keypair::{
spseq_uc::{Credential, CredentialCompressed},
verify_proof, CBORCodec, CredProof, Initial, Issuer, IssuerError, IssuerPublic, MaxCardinality,
Nym, NymPublic, Offer, Randomized, Secret, VK,
};
pub use zkp::Nonce;
#[cfg(target_arch = "wasm32")]
pub use set_commits::test_aggregate_verify_cross;
#[cfg(doctest)]
pub struct ReadmeDoctests;
pub struct CredentialBuilder<'a> {
entries: Vec<Entry>,
extendable: usize,
issuer: &'a Issuer,
}
impl<'a> CredentialBuilder<'a> {
pub fn new(issuer: &'a Issuer) -> Self {
Self {
issuer,
entries: Vec::new(),
extendable: 0,
}
}
pub fn with_entry(&mut self, entry: Entry) -> &mut Self {
self.entries.push(entry);
self
}
pub fn max_entries(&mut self, extendable: &usize) -> &mut Self {
self.extendable = *extendable;
self
}
pub fn issue_to(
&self,
nym_proof: &NymProof,
nonce: Option<&Nonce>,
) -> Result<Credential, IssuerError> {
let k_prime = self.extendable.checked_sub(0);
self.issuer
.issue_cred(&self.entries, k_prime, nym_proof, nonce)
}
}
pub struct OfferBuilder<'a, Stage> {
our_nym: &'a keypair::Nym<Stage>,
credential: &'a Credential,
unprovable_attributes: Vec<Attribute>,
current_entries: Vec<Entry>,
additional_entry: Option<Entry>,
max_entries: usize,
}
impl<'a, Stage> OfferBuilder<'a, Stage> {
pub fn new(
our_nym: &'a keypair::Nym<Stage>,
credential: &'a Credential,
current_entries: &[Entry],
) -> Self {
Self {
our_nym,
credential,
unprovable_attributes: Vec::new(),
current_entries: current_entries.to_vec(),
additional_entry: None,
max_entries: credential.update_key.as_ref().map_or(0, |k| k.len()),
}
}
pub fn without_attribute(&mut self, redacted: Attribute) -> &mut Self {
self.unprovable_attributes.push(redacted);
self
}
pub fn additional_entry(&mut self, entry: Entry) -> &mut Self {
self.additional_entry = Some(entry);
self
}
pub fn max_entries(&mut self, limit: usize) -> &mut Self {
self.max_entries = std::cmp::min(
limit,
self.credential.update_key.as_ref().map_or(0, |k| k.len()),
);
self
}
pub fn open_offer(&self) -> Result<(keypair::Offer, Vec<Entry>), error::Error> {
let mut cred_redacted = self.credential.clone();
let mut provable_entries = self.current_entries.clone();
if !self.unprovable_attributes.is_empty() {
let mut opening_vector_restricted = self.credential.opening_vector.clone();
for unprovable_attribute in &self.unprovable_attributes {
for (index, entry) in self.current_entries.iter().enumerate() {
if entry.contains(unprovable_attribute) {
opening_vector_restricted[index] = Scalar::ZERO;
provable_entries[index] = Entry::new(&[]);
}
}
}
cred_redacted = Credential {
opening_vector: opening_vector_restricted,
..cred_redacted
};
}
if let Some(entry) = &self.additional_entry {
provable_entries.push(entry.clone());
}
cred_redacted.update_key = match self.max_entries {
0 => None,
_ => Some(
self.credential
.update_key
.as_ref()
.unwrap()
.iter()
.take(self.max_entries)
.cloned()
.collect::<Vec<_>>(),
),
};
let offer = self.our_nym.offer(&cred_redacted, &self.additional_entry)?;
Ok((offer, provable_entries))
}
}
pub struct ProofBuilder<'a, Stage> {
nym: &'a keypair::Nym<Stage>,
cred: &'a Credential,
all_attributes: Vec<Entry>,
selected_attributes: Vec<Attribute>,
}
impl<'a, Stage> ProofBuilder<'a, Stage> {
pub fn new(
nym: &'a keypair::Nym<Stage>,
cred: &'a Credential,
all_attributes: &[Entry],
) -> Self {
Self {
nym,
cred,
all_attributes: all_attributes.to_vec(),
selected_attributes: Vec::new(),
}
}
pub fn select_attribute(&mut self, attribute: Attribute) -> &mut Self {
self.selected_attributes.push(attribute);
self
}
pub fn prove(&self, nonce: &Nonce) -> (keypair::CredProof, Vec<Entry>) {
let selected_attr = self
.all_attributes
.iter()
.map(
|entry| {
entry
.iter()
.filter(|attr| self.selected_attributes.contains(attr))
.cloned()
.collect::<Entry>()
},
)
.collect::<Vec<Entry>>();
let proof = self
.nym
.prove(self.cred, &self.all_attributes, &selected_attr, nonce);
(proof, selected_attr)
}
}
#[cfg(test)]
mod lib_api_tests {
use lazy_static::lazy_static;
use super::*;
lazy_static! {
static ref NONCE: Nonce = Nonce(Scalar::from(42u64));
}
#[test]
fn test_credential_building() -> Result<()> {
let issuer = Issuer::default();
let nym = Nym::new();
let over_21 = Attribute::new("age > 21");
let seniors_discount = Attribute::new("age > 65");
let root_entry = Entry::new(&[over_21, seniors_discount]);
let cred = issuer
.credential() .with_entry(root_entry.clone()) .max_entries(&MaxEntries::default()) .issue_to(&nym.nym_proof(&NONCE), Some(&NONCE))?;
assert_eq!(cred.commitment_vector.len(), 1);
let cred = CredentialBuilder::new(&issuer)
.with_entry(root_entry.clone())
.max_entries(&MaxEntries::default())
.issue_to(&nym.nym_proof(&NONCE), Some(&NONCE))?;
assert_eq!(cred.commitment_vector.len(), 1);
let cred = CredentialBuilder::new(&issuer)
.with_entry(root_entry.clone())
.max_entries(&MaxEntries::default())
.issue_to(&nym.nym_proof(&NONCE), Some(&NONCE))?;
assert_eq!(cred.commitment_vector.len(), 1);
assert_eq!(
cred.update_key.as_ref().unwrap().len(),
MaxEntries::default()
);
let another_entry = Entry::new(&[Attribute::new("another entry")]);
let cred = issuer
.credential()
.with_entry(root_entry.clone())
.with_entry(another_entry)
.issue_to(&nym.nym_proof(&NONCE), Some(&NONCE))?;
assert_eq!(cred.commitment_vector.len(), 2);
assert!(cred.update_key.is_none());
let proof = nym.prove(&cred, &[root_entry.clone()], &[root_entry.clone()], &NONCE);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&[root_entry],
Some(&NONCE)
));
Ok(())
}
#[test]
fn offer_tests() -> Result<()> {
let issuer = Issuer::default();
let alice_nym = Nym::new();
let bobby_nym = Nym::new();
let charlie_nym = Nym::new();
let doug_nym = Nym::new();
let evan_nym = Nym::new();
let over_21 = Attribute::new("age > 21");
let seniors_discount = Attribute::new("age > 65");
let root_entry = Entry::new(&[over_21.clone(), seniors_discount]);
let cred = match issuer
.credential()
.with_entry(root_entry.clone())
.max_entries(&MaxEntries::default()) .issue_to(&alice_nym.nym_proof(&NONCE), Some(&NONCE))
{
Ok(cred) => cred,
Err(e) => panic!("Error issuing cred: {:?}", e),
};
let (offer, provable_entries) =
alice_nym.offer_builder(&cred, &[root_entry]).open_offer()?;
let bobby_cred = bobby_nym.accept(&offer)?;
let proof = bobby_nym.prove(&bobby_cred, &provable_entries, &provable_entries, &NONCE);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&provable_entries,
Some(&NONCE)
));
let (proof, selected_entries) = bobby_nym
.proof_builder(&bobby_cred, &provable_entries)
.select_attribute(over_21)
.prove(&NONCE);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&selected_entries,
Some(&NONCE)
));
let handsome_attribute = Attribute::new("also handsome");
let additional_entry = Entry::new(&[handsome_attribute.clone()]);
let (offer, provable_entries) = bobby_nym
.offer_builder(&bobby_cred, &provable_entries)
.additional_entry(additional_entry)
.open_offer()?;
let charlie_cred = charlie_nym.accept(&offer)?;
let (proof, selected_entries) = charlie_nym
.proof_builder(&charlie_cred, &provable_entries)
.select_attribute(handsome_attribute.clone())
.prove(&NONCE);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&selected_entries,
Some(&NONCE)
));
let (offer, provable_entries) = charlie_nym
.offer_builder(&charlie_cred, &provable_entries)
.without_attribute(handsome_attribute.clone())
.open_offer()?;
assert_eq!(provable_entries.len(), 2); assert_eq!(provable_entries[0].len(), 2); assert_eq!(provable_entries[1].len(), 0);
let doug_cred = doug_nym.accept(&offer)?;
let (_proof, selected_entries) = doug_nym
.proof_builder(&doug_cred, &provable_entries)
.select_attribute(handsome_attribute.clone())
.prove(&NONCE);
let contains_handsome = selected_entries
.into_iter()
.any(|entry| entry.contains(&handsome_attribute));
assert!(!contains_handsome);
let (offer, provable_entries) = doug_nym
.offer_builder(&doug_cred, &provable_entries)
.max_entries(3)
.open_offer()?;
let evan_entry = Entry::new(&[Attribute::new("evan entry #1")]);
let evan_cred = evan_nym.accept(&offer)?;
let even_nym_2 = Nym::new();
let (offer, provable_entries) = evan_nym
.offer_builder(&evan_cred, &provable_entries)
.additional_entry(evan_entry)
.open_offer()?;
let evan_2_cred = even_nym_2.accept(&offer)?;
let (proof, selected_entries) = even_nym_2
.proof_builder(&evan_2_cred, &provable_entries)
.select_attribute(Attribute::new("evan entry #1"))
.prove(&NONCE);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&selected_entries,
Some(&NONCE)
));
let res = even_nym_2
.offer_builder(&evan_2_cred, &provable_entries)
.additional_entry(Entry::new(&[Attribute::new("bigger than Max Entry")]))
.open_offer();
assert!(res.is_err());
Ok(())
}
#[test]
fn test_delegate_root_cred() -> Result<()> {
let issuer = Issuer::default();
let nym = Nym::new();
let root_entry = Entry::new(&[]);
let nonce = Nonce::default();
let cred = match issuer
.credential()
.with_entry(root_entry.clone())
.max_entries(&MaxEntries::default()) .issue_to(&nym.nym_proof(&nonce), Some(&nonce))
{
Ok(cred) => cred,
Err(e) => panic!("Error issuing cred: {:?}", e),
};
let del_root_entry = Attribute::new("delegated root entry");
let (offer, entries) = nym
.offer_builder(&cred, &[root_entry])
.additional_entry(Entry::new(&[del_root_entry.clone()]))
.open_offer()?;
let del_nym = Nym::new();
let del_cred = del_nym.accept(&offer)?;
let (proof, selected_entries) = del_nym
.proof_builder(&del_cred, &entries)
.select_attribute(del_root_entry)
.prove(&nonce);
assert!(keypair::verify_proof(
&issuer.public,
&proof,
&selected_entries,
Some(&nonce)
));
Ok(())
}
}