2 releases

0.2.1 Jul 7, 2024
0.2.0 Jul 6, 2024

#465 in Authentication


Used in delano-wallet-core

MIT license

280KB
4.5K SLoC

Delegatable Anonymous Credentials (Delanocreds)

This library enables you to create, issue, delegate/extend/restrict/transfer, and verify credentials in an anonymous way.

Create Root Credentials, then delegate the ability to add or remove credentials, without revealing the identity of any of the holders in the delegation chain, including the prover!

Useful if you want the ability to delegate credentials, capabilities, or other data without revealing the identity of the delegation or holder(s).

Holders can also selectively prove attributes and remove the ability for delegatees to prove selected attributes.

Example Explainer

Project Status / Roadmap

API works and is stabilizing, but may change in the future.

Roadmap:

  • Passing tests
  • Basic Public API
  • Stable API
  • DelanoWallet (Store, Sign, Backup Credentials)
  • DelanoNet (Data Exchange Network)

Delegation

There are a few ways the delegation can be used:

Delegate adding Entries

We can delegate the ability to add Entries (up to MaxEntries), but only if the credential is marked as extendable and the available Entry slots have not all been used by previous delegatees.

Even if there is no ability for a credential holder to add additional attributes, the following always holds true:

  • Credentials holders can always assign their credential to a new public key
  • Attributes are always able to be selectively shown or hidden by a holder

It is important to note that whether the current holder can add Entries or not, the Credential can always be assigned to a new public key, that is what makes this scheme anonymizable.

Therefore, while holders may restrict the ability of delegatees to add attributes, they will always be able to assign the credential to a new public key for the attributes they do have.

Issuer Delegation

This is where things get really powerful. The Root Issuer can delegate a blank credential to a server's Public Key, enabling the server to Issue credentials on it's behalf without ever giving up the root secret!

Example: You want to set up an email campaign to email contacts a Credential that allows them to prove they own that email address. Of course you want a bot to process the form for you instead of doing it by hand. You issue a delegatable credential to the Server's Public Key. Now only that Public Key can issue credentials on that empty Root credential, and if the keys are exposed because of a server hack you don't expose your root secret keys. Additional protocols can be layered on top such as adding an expiry date to the original credential, invalidating any credential that was issued after this date with a stolen key.

Prover Delegation

Not online all the time to generate proofs? No problem! Users can actually even delegate proving of a credential to another (say, a server) who can generate the proof on their behalf, again without having to expose any of their secret keys. The User can disable additonal entries, or even redact entries, restricting the abilities of the server to change entry attributes or prove certain entries. Because the Attributes are all Sha2-256 hashed, the server does not even know the attribute values! In the event of stolen keys, all the theif would be able to do is make more proofs on our behalf (thank you for the free services).

Root Issuer Summary of Choices/Options:

  • Maxiumum Attribute Entries: Maximum number of Entrys per Credential.
  • Maximum Cardinality: Maximum selectable number of Attributes per Proof or Entry

Attributes

Attributes can be created from any bytes, such as age > 21 or even a .jpg file. These bytes are hashed usin Sha2-256, which means they are also content addressable! Once an Attribute is created, it can be referenced by it's Content Identifier (CID) instead of the value itself. This means we can also refer to attributes by their CID, and not have to worry about revealing the actual content of the attribute or re-hashing the content when it needs to be referenced.

The algorithm used to hash the attributes is Sha2-256, with a length of 32 bytes which is the typical 32 byte digest commonly seen. If you try to create an Attribute out of a CID with a different hash or length, it will fail and result in an error. For this reason, use the Attribute methods provided by this library when creating Attributes.

use delanocreds::Attribute;

let some_test_attr = "read";

let read_attr = Attribute::new(some_test_attr); // using the new method

// Try Attribute from cid
let attr_from_cid = Attribute::try_from(read_attr.cid()).unwrap(); // Error if wrong type of CID
assert_eq!(read_attr, attr_from_cid);

// Attribute from_cid
let attr_from_cid = Attribute::from_cid(&read_attr).expect("a Sha2-256 hash type"); // Returns `None` if wrong type of CID
assert_eq!(read_attr, attr_from_cid);

If an Attribute is public, then you could store it to a content addressed storage like IPFS. If the Attribute is sensitive, then the Attribute can just be referred to by it's hash.

Note that if someone has both a Credential Offer and the Attributes or even their hashes, they will be able to claim the Credential, so you want to keep Offers and their associated Attributes separate (or securely transported if together).

Entries

A Credential is comprised of one or more Entrys. Each Entry contains one or more Attributes. Entries are used to group Attributes together.

Attribute Entries:
==> Entry Level 0: [Attribute, Attribute, Attribute]
==> Entry Level 1: [Attribute]
==> Entry Level 2: [Attribute, Attribute]
==> Additonal Entry? Only if 3 < Extendable < MaxEntries

Redact

Holders of a Credential can redact any Entry from the Credential, which will remove the ability to create proofs on any Attributes in that Entry. This is useful if you want to remove the ability for a delegatee to prove a specific attribute, but still allow them to prove other attributes. It is important to note that if the ability to prove an Attribute is removed before delegating a Credential, then the entire Entry is removed -- not just the single Attribute. If you want to still allow a delegtee to use the other Attributes, you must create a new Entry with the other Attributes and delegate the extended Credential.

This is done by zeroizing the Opening Information for the Entry commitment in the Credential so a proof cannot be created.

Bindings

The intention is to provide the following bindings:

Rust API

Current full API is available by looking at the src/lib.rs tests. Below is a small sampling of how to use the API.

use anyhow::Result;
use delanocreds::{Issuer, Nym, verify_proof, Nonce, Entry, MaxEntries, Attribute};

fn main() -> Result<()> {
    // Build a RootIssuer with ./config.rs default sizes
    let mut issuer = Issuer::default();

    // Create Entry of 2 Attributes
    let over_21 = Attribute::new("age > 21");
    let seniors_discount = Attribute::new("age > 65");
    let root_entry = Entry::new(&[over_21.clone(), seniors_discount.clone()]);

    // Along comes Alice's (pseudo)nym
    let alice_nym = Nym::new();

    // In order for Alice to be issued a Root Credential from the Issuer, the Nym must be randomized to keep her anonymous
    // as non-randomized Nym's are used only to accept Credentials.
    let alice_nym = alice_nym.randomize();

    // A verifier can demand the nym proof include a nonce to prevent replay attacks, or it can skip with with `None`
    // The nonce can be compared against the Pedersen open randomness in the `NymProof` to verify that a replay
    // attacker isn't reusing a previously generated proof
    let nonce = Nonce::default(); // generates a random nonce for us

    // Give a nonce to Alice so she can generate a NymProof using it
    let nym_proof = alice_nym.nym_proof(&nonce);

    let cred = issuer
        .credential() // CredentialBuilder for this Issuer
        .with_entry(root_entry.clone()) // adds a Root Entry
        .max_entries(&MaxEntries::default()) // set the Entry ceiling
        .issue_to(&nym_proof, Some(&nonce))?; // issues to a Nym

    // Send the (powerful) Root Credential, Attributes, and Entrys to Alice

    // Alice can use the Credential to prove she is over 21
    let (proof, selected_attributes) = alice_nym.proof_builder(&cred, &[root_entry.clone()])
        .select_attribute(over_21.clone())
        .prove(&nonce);

    assert!(verify_proof(&issuer.public, &proof, &selected_attributes, Some(&nonce)));

    // Alice can offer variations of the Credential to others
    let bobby_nym = Nym::new();

    let (offer, provable_entries) = alice_nym.offer_builder(&cred, &[root_entry])
        .without_attribute(seniors_discount) // resticts the ability to prove attribute Entry (note: Removes the entire Entry, not just one Attribute)
        .additional_entry(Entry::new(&[Attribute::new("10% off")])) // adds a new Entry
        .max_entries(3) // restrict delegatees to only 3 entries total
        .open_offer()?;

    // Send to Bob so he can accept the Credential
    let bobby_cred = bobby_nym.accept(&offer)?;

    // and prove all entries
    let (proof, selected_attributes) = bobby_nym.proof_builder(&bobby_cred, &provable_entries)
        .select_attribute(over_21)
        .prove(&nonce);

    assert!(verify_proof(&issuer.public, &proof, &selected_attributes, Some(&nonce)));

    Ok(())
}

Features

Advantages

This DAC scheme has the following advantages over other anonymous credential schemes:

  • Attributes: User can selectively disclose and prove some of the attributes in the credential.
  • Expressiveness: S (selective disclosure), R (arbitrary computable relations over attributes, meaning you can do more than just selective disclosure)
  • Rest: Means whether it is possible to apply a restriction on the delegator’s power during the delegation.
  • Selective Anonymity: Strong anonymity guarantees meaning that no one can trace or learn information about the user’s identity or anything beyond what they suppose to show during both the issuing/delegation and showing of credentials.
  • Credential Size: O(1), meaning the size of the credential is constant.
  • Show Size: O(L), meaning the size of the showing grows linearly in the number of delegations.
  • Undisclosed attributes: O(u), meaning the size of the undisclosed attributes grows linearly in the number of delegations.

Table 1. Comparison of practical DAC schemes

Scheme Attributes Expressiveness Rest Selective Anonymity Credential Size Show Size
BB18 ✔️ S/R 🌓† O(1) O(u)
CDD ✔️ S/R ✖️ 🌗♣ O(nL) O(uL)
CL ✖️ ✖️ 🌙* O(nL) O(uL)
This ✔️ S ✔️ 🌚‡ O(1) O(L)

🌓† Requires a trusted setup and have a trapdoor associated to their parameters.

🌗♣ It does not support an anonymous delegation phase.

🌙∗ It also allows an adversarial CA but no delegators’s keys leaks.

🌚‡ We consider a malicious issuer key CA and all delegators keys can be exposed.

Hashing

RFC9380 recommends expanded message digest (XMD) for BLS12-381 curves when hashing to curve (as opposed to extendable-output function (XOF) for Sha3 SHAKE). Libraries that support XMD are blst and pairing_plus.

Tests

cargo test
cargo test --target wasm32-unknown-unknown

Speed

This library can generate, issue, delegate, prove and verify 30 selected credentials out of 100 issued attributes in less than 400ms on Intel i5-3470 CPU @ 3.20GHz.

That's fast enough for time-critical applications like public transportation, ticketing, etc.

Quick Bench

It's fast. Selecting 30 attributes out of 96 total, the following benchmarks were observed for each step:

After running cargo run --release:

Step Time (ms)
Setup 100
Issue 81
Offer 5
Accept 69
Prove 19
Verify 72
============= =========
Total 346

Bench Variables:

  • l - upper bound for the length of the commitment vector
  • t - upper bound for the cardinality of the committed sets
  • n < t - number of attributes in each attribute set A_i in commitment
  • C_i (same for each commitment level)
  • k - length of attribute set vector
  • k_prime - number of attributes sets which can be delegated

We set the above parameters as t = 25, l = 15, k = 4, k' = 7 and n = 10 to cover many different use-cases

Assumption:

  • each time a credential is delegated, an attribute is added

Docs

cargo doc --workspace --no-deps --open

To build the docs incrementally, use cargo watch -x 'doc --workspace --no-deps --open'.

Build the docs in Windows for Github pages: ./build_docs.bat

References

Rust implementation of https://github.com/mir-omid/DAC-from-EQS in paper (PDF).

Dependencies

~8–11MB
~197K SLoC