2 releases
0.2.1 | Jul 7, 2024 |
---|---|
0.2.0 | Jul 6, 2024 |
#228 in Authentication
Used in delano-wallet-core
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.
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
Entry
s perCredential
. - Maximum Cardinality: Maximum selectable number of
Attributes
perProof
orEntry
Attributes
Attribute
s 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 Attribute
s.
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 Offer
s and their associated Attributes
separate (or securely transported if together).
Entries
A Credential
is comprised of one or more Entry
s. Each Entry
contains one or more Attribute
s. Entries are used to group Attribute
s 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 Attribute
s 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
- wasm bindgen (wasm32-unknown-unknown)
- wasm interface types (WIT)
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