This repository contains the basis for the AnonCreds v2 implementation, an evolution of the AnonCreds v1 open specification and open source implementation that has been widely used around the world since 2017. The goal of AnonCreds v2 is to retain and extend the privacy-preserving features of AnonCreds v1, while improving capabilities, performance, extensibility, and security.
Want to help with AnonCreds v2? Check out the To Do Items to extend this implementation from a starting point to where it can be used in production solutions.
Concretely, AnonCreds v2:
AnonCreds v2 is currently in production and ready for use. This repository is but an initial (thorough but incomplete) implementation. The issues contain a list of the labelled To Do Items (open and closed) that we envision are needed for a complete implementation. We are now seeking collaborators to help with the implementation to knock off the To Do Items.
Some of the To Do Items are focused around making AnonCreds v2 sufficiently opinionated to enable interoperability. The success to date of AnonCreds to now has been because of its ZKP-capabilities combined with its opinionated specification that makes it obvious to all parties in an AnonCreds verifiable credential ecosystem to know what is required of each other. Comparable opinionated specifications are needed in AnonCreds v2 to simplify deployments.
The following is an overview of the current AnonCreds v2 implementation.
Credentials are composed of claims. Claims are a tuple of information consisting of a label, type, and value. The label is distinct per credential allowing a reference to the claim by a user-friendly name. For each type there is a mapping from the content value to the cryptographic value–the form that can be used by the cryptographic algorithms. AnonCreds v2 supports the following types: integers, dates, enumerations, raw cryptographic material like secret keys or pseudonyms, and arbitrary length data like strings, images and biometrics. Arbitrary length data and enumerations are mapped using a hash function, integers are restricted to 64 bits and do not require mapping. Dates are mapped to integers based on the resolution needed. For example, birthdays could be encoded as the number of days since 1/1/1900. Timestamps with precision to the second are the number of seconds since 1/1/1970.
Each of these is represented as the types
EnumerationClaim
- Defines a fixed set of values where the claim value is one of them.NumberClaim
- A claim is a 64-bit integer like numbers, dates, and times.HashedClaim
- The claim is an arbitrary length value that will be hashed like strings, images, and biometrics.ScalarClaim
- The claim is already a cryptographic value and should be taken as is like a secret key.RevocationClaim
- The claim is meant to indicate revocation status of the claim.Claims can also be checked against a set of validators which can be zero or more of the following:
LengthValidator
checks the claim's length is correct.
RangeValidator
checks if the claim value is between the correct range. The claim value is interpreted as an
integer. The result is false if the value is not in range or cannot be interpreted as an integer. The
ranges are inclusive
RegexValidator
check if the claim data matches a regular expression
AnyOne
check if a claim value matches any value in the list.
The label, type, value, and validators are combined into a ClaimSchema
. An ordered list of ClaimSchema
's
can be used to create a CredentialSchema
The schema is data definition and layout for the credential. The schema defines what data is included in the credential, how it should be interpreted, what rules the data must follow to be well formed, and which claims are allowed to be blindly signed.
let cred_schema = CredentialSchema::new(Some(LABEL), Some(DESCRIPTION), &[], &schema_claims)?;
^ ^ ^ ^
| | | |
The Label | | |
The description | |
| The claim schemas
|
The claim labels of blindable claims
Once a schema has been created, any number of issuers can be created.
The issuer manages a set of keys for signing credentials, decrypting verifiable encryptions, and updating revocation registries. The secret keys are never communicated or disclosed and should be stored in secure vaults. The public keys contain cryptographic information for verification. The public keys should be accessible for holders and verifiers to use and trust. These keys can be anchored to a blockchain, S3 bucket, IPFS, or CDN.
let (issuer_public, issuer) = Issuer::new(&cred_schema);
In the code example above, the issuer_public
can be given to anyone while issuer
must be kept private.
issuer_public
allows anyone to verify the credential (or presentation) was signed by the issuer.
issuer
contains the secret signing keys and should be protected.
This protocol has a Holder role, executed by the end user, and the Issuer role, executed by an organization (e.g. business or government). During the issuance protocol, the Issuer and Holder interactively create a signature for the Holder, which is the cryptographic part of the credential. Credentials can be issued entirely by the Issuer or a three step blind signing protocol. At each step of this three flow protocol, a zero knowledge proof ensures that both parties are correctly implementing the protocol. The Holder first computes signature with Blind and sends the commitment and proof of knowledge of blind messages to the Issuer. The Issuer verifies the proof and computes the blind signature using Blind Sign. The blind signature and known messages are sent to the Holder who unblinds the signature with Unblind and uses Verification to check the signature validity.
The issuer signs the ClaimData
which becomes the credential. The ClaimData
must match the same order as the
credential schema otherwise issuance will fail.
let credential = issuer.sign_credential(&[
RevocationClaim::from(CRED_ID).into(),
HashedClaim::from("John Doe").into(),
HashedClaim::from("P Sherman 42 Wallaby Way Sydney").into(),
NumberClaim::from(30303).into(),
])?;
This protocol is executed by the Issuer role. When Issuers revoke a credential, the revocation registry update is published and all existing revocation handles become stale—holders cannot show that their credential has not been revoked for the newer version–thus users need to update their handles. Users can request an updated revocation handle from the Issuer at any time with a presentation that proves the previous handle was valid. The Issuer checks if that user’s signature is not currently revoked and if still valid, returns a fresh handle. Revocation handles can be used for unlimited presentations until the next revocation update occurs. This model is different from current PKI in that PKI requires users to reveal a unique ID or handle to the verifier who then queries the Issue directly or indirectly downloads a list to verify the ID or handle has not been revoked. This newer model requires no such check. Instead, users use their handles to prove they are not revoked to verifiers. The verifier only needs the revocation verifying key and registry value to validate the proof. Issuers revoke a credential with Remove and publish the updated accumulator value.
This protocol is executed between the Holder and the Issuer roles. After publishing an updated accumulator, the Holder’s revocation handle will be stale. The Issuer checks revocation handle claim to see if it's still in the non-revoked set. If successful, Witness generation is used to create and return a new witness handle for the Holder.
A presentation schema is similar to a credential schema in that it defines a set of statements and conditions that a user must satisfy for the verifier to be convinced about the veracity of the user’s interaction. Statements indicate what must be proved. A credential from a specific issuer or a revocation check are examples of statements. Each statement contains a unique identifier, the statement type, and the public data used to verify the statement like keys or numeric constants.
To combine and connect all the statements contained in the specification, that is glue all parts together into a single ZKP, we use schnorr proofs. For generating a schnorr proof that encompasses multiple statements, for each statement one requires a hash contribution on the one hand, a proof contribution based on an aggregation of the hash contributions on the other. Thus, each statement contributes to the aggregated proof. The hash contributions are done on the generation side, and proof contributions on the verifier side.
Ultimately, claims are disclosed or remain hidden. Disclosed claims are revealed to the verifier. Hidden claims can be unrevealed completely or partially revealed in predicate statements.
All statements except signatures represent predicate statements. Predicates can prove a claim is in a set, in a range equal to a previously sent but different message, or encrypted in a ciphertext.
AnonCreds v2 supports the following presentation statement types:
SignatureStatement
defines which issuer a signature must come from and which claims must be disclosed.AccumulatorSetMembershipStatement
defines a proof where the claim is a member of the set. The claim is not disclosed, only if it is a member of the set.EqualityStatement
is used to check that a non-disclosed claim is the same across multiple other statements.CommtimentStatement
creates a unique value based on a claim. Is also used to link to range statements.RangeStatement
defines a proof where a claim is in a range. Requires a commitment statement for the specified claim.VerifiableEncryptionStatement
defines a proof where a claim is proven to be encrypted in a ciphertext.A presentation can be created by taking a list of credentials and a presentation schema. A unique presentation id called a nonce should also be included to prevent a holder from reusing a presentation previously sent. This shows the holder is able to create the presentation fresh and not copy it from another holder including themselves.
let presentation = Presentation::create(&credentials, &presentation_schema, &nonce)?;
To verify the presentation, the verifier simply calls verify using the same public information the holder used.
presentation.verify(&presentation_schema, &nonce)?;
To run the cargo
tests for AnonCreds, fork/clone this repository and in the root folder,
execute the following. Rust must be installed on your system. The code currently compiles
into a crate called credx
.
cargo tests
Take a look at tests/flow.rs to see the objects in AnonCreds v2, especially the issuance input credential schema and presentation statements.
A special thanks to Cryptid Technologies, Inc for sponsoring and collaboration in development of this library.
Licensed under Apache License, Version 2.0, (LICENSE or https://www.apache.org/licenses/LICENSE-2.0)