Anonymous Credentials
Anonymous credential scheme based on https://eprint.iacr.org/2017/115.pdf
Usage
In an anonymous credential system you have three actors; issuers that
acts as a trust anchor and issue certificates, verifiers who check credentials
as part of registration or authentication, and users who posses certificates.
Certificates are akin to SSL certificates, but also very different. An issuer
verifies the information that goes into the certificate out of band, eg checking
public registers, notarized documents etc. It will then issue a certificate to
the user, which the user in turn can create credentials from. Credentials prove
a subset of the attributes contained in the certificate, but in a zero knowledge
manner. From a certificate, infinite credentials can be made, and they are
unlinkable, meaning two credentials cannot be traced back to the same
certificate, except if one gets revoked. A credential can be made from a
certificate without contacting the issuer, making them ideal as an trust
mechanism in distributed systems.
A fresh system is initialised as follows:
First an issuer must create their certifications based on a "schema". They can
then publish which certifications they provide, so verifiers and users can
choose which they want to accept and apply for, respectively.
Verifiers can then start offering their services, guarded by a valid
credential under a given certification.
Users can now apply for a certificate from an issuer, and after successful
application, can start using their certificate.
Verifiers must make public which certifications they accept, and which
attributes from a given certification must be proven. Users can fetch this, and
create a new credential to show the verifier they possess a certificate
satisfying the attributes required by the verifier.
Issuer
const issuer = new Issuer('./some-storage-path')
const schema = {
"age": "number",
"nationality": "string",
"residence": "string",
"drivers licence": "boolean",
"employed": "boolean",
"gender": "string"
}
issuer.addCertification(schema, (err, certId) => {
if (err) throw err
const certInfo = issuer.getCertInfo(certId)
})
Now the issuer is ready to grant credentials from the certification.
Verifier
A new verifier must first add the certifications it is willing to accept.
They may request the necessary info associated with certId
from the issuer
const verifier = new Verifier('./some-other-storage')
verifier.registerCertification(certInfo, () => {
})
User
The user should have obtained certId
from a public issuer list. Now they can
apply for a certificate from an issuer:
const user = new User()
const application = {
"age": 66,
"nationality": "italy",
"residence": "austria",
"drivers licence": true,
"employed": true,
"gender": "male"
}
const app = user.createApplication(application, certId)
(app, { metadata }) --> server
(app) => {
const issuanceInit = issuer.beginIssuance(app)
}
(issuanceInit) --> client
(issuanceInit) => {
const issuanceResponse = user.obtain(issuanceInit)
}
(issuanceResponse) --> server
(issuanceResponse) => {
const final = issuer.grantCredential(issuanceResponse)
}
(final) --> client
(final) => {
user.store(final)
}
Now the user has a credential, which they may present to a verifier:
const transcript = user.present(['age', 'nationality'])
(transcript) --> verifier
verifier.validate(transcript, (err, identifier) => {
if (err) {
}
})
If a malicious user is detected, the transcript
associated with their
credential may be reported to the issuer and, if appropriate, the issuer may
revoke the entire credential:
(identifier, { incidentReport }) --> issuer
(identifier) => {
issuer.revokeCredential(identifier, (err) => {
})
}
API
Issuer
const org = new Issuer(storage)
Instatiate a new Issuer instance. storage
designates the path which shall be
used to store revocation list information.
org.addCertification(schema, cb(err, certId))
Register a new certification. Takes a JSON schema
specifying field titles and
types and returns the resulting certification's certId
, a unique identifier
string, to the callback provided.
The certification is stored in issuer.certifications
under it's certId
and
may be accessed by issuer.certifications[certId]
.
const certInfo = org.getPublicCert(certId)
Get the public keys and revocation list information associated with a given
certId
. This info is passed to a verifier for them to recognise
new certifications. certInfo
is returned as a buffer
containing the
serialized information to be passed to a verifier.
for (const [certId, certInfo] of org.getPublicCerts())
Iterate over all public certifications as a list of pairs of certId
and
certInfo
. See the above method for the encoding.
org.beginIssuance(application)
Begin a new issuance protocol. This method takes a user's application
, which is the output of user.apply
and outputs a setup
object, encoding blinded curve points that are used to generate the credential, which may be passed straight to the user.
org.grantCredential(res)
This is the Issuer's final step during issuance and takes the output of user.obtain
. In this step the Issuer contributes entropy towards the users credential and seals the credential by exponentiating the product of all curve points in the credential by the Issuer's secret key. This term is used in a bilinear pairing equality to verify the sum of exponents during verification of the credential.
org.revokeCredential(identifier, cb(err))
Revoke a credential associated with a given identifier
. This method shall publish the root id associated with this key to the certifications revocation list, anyone subscribed to the revocation list may then derive all keys associated with the root id and checks against these keys during verification.
User
const user = new User()
Instantiate a new User.
const application = user.createApplication(details, certId)
Generate an application with the relevant details to send to the Issuer responsible for certId
. When sending this to the issuer, this should be accompanied by a document proving these properties, e.g. photo ID.
const issuanceResponse = user.obtain(msg)
The user's contribution in the issuance protocol. This takes the output of issuer.addIssuance
as a buffer
and returns an buffer
containing the serialized response.
In this step the user generates random scalars used to exponentiate the blinded curve points received from addIssuance
message and returns them to the issuer, thereby contributing her own entropy to the certificate.
user.store(msg)
Store a completed credential. msg
should be a buffer
outputted by a call to issuer.grantCredential
, which contains the serialization of the finalised credential and the issuer's signatures for all pseudonyms associated with this id.
This will be stored internally as a new Identity
, which has associated with it a credential as well as the root
from which all pseudonyms are derived. This Identity
can be later accessed using the findId
method below or accessed directly from user.identities
.
const transcript = user.present(attributes, certId)
Generate a transcript showing a valid credential, only disclosing the properties specified in attributes
. properties
should be passed as an array
of strings
, e.g ['age', 'nationality']
; an appropriate identity with the required attributes is then chosen to present.
If certId
is given, only identities belonging to this certId
shall be selected, returning undefined
if no suitable identity is found.
Returns a buffer
containing the serialized data required by a verifier to validate the credential. transcript
should be passed as an argument to verifier.validate
const id = user.findId(required)
Access an identity containing the attributes listed in required
. Takes an array
of strings
, e.g ['age', 'nationality']
, and returns id
as an instance of an Identity
object.
Verifier
const verifier = new Verifier(storage)
Instantiate a new Verifier. storage
should be a path designated where revocation list data shall be stored.
async verifier.registerCertification(cert, cb())
Recognise a new certification. cert
is a buffer
as outputted of org.getCertInfo(certId)
, containing the serialization of the certification public keys and the information needed to sync the revocation list
associated with the certification.
async verifier.validate(transcript, cb(err, identifier))
Validate a given transcript
, which is the buffer
returned by user.present
. cb
should have the signature cb(err)
. An error message shall be passed to cb
if validation fails.
identifier
is needed when reporting bad users to the issuing party, therefore it should be associated with that user account.
How it works
Credential
The credential scheme itself is described in An efficient self-blindable attribute-based credential scheme. The principle is that each field has an associated curve point in G1. The user's attributes are encoded as scalars and used exponentiate their associated curve point. Express the product of these exponentiated curve points as C
, the issuer provides a signature over this product by exponentiating C
by a secret key, z
, yielding T = C * z
. The user's public key is the tuple of curve points in G1 and the corresponding secret key is the tuple of associated attributes encoded as scalars.
When showing the credential, the user presents it's public key and the attributes they wish to disclose. We exponentiate the curve points associated with disclosed attributes and negate the terms and then calculate the product of these terms as D = S1-k1 * S3-k3 * ... * Si-ki (for all i in disclosure set). To prove they know the undisclosed terms, the user can negate C and demonstrate proof of knowledge of all terms needed to satisfy: D = C-1 * S0k0 * S2k2 * ... * Sjkj (for all j in undisclosed set).
Once this has been proved, the verifier uses a bilinear pairing to check that the relationship T = C * z
still holds and a second pairing to verifier the user's public key against the issuers public key.
Blinding
Because bilinear pairing is verifying the product of exponents, we may exponentiate both sides of the equality with the same blinding factor whilst maintaining the pairing equality. This allows the user to 'self blind' their credential, thus ensuring unlinkability between separate showings.
Pseudonym
The unlinkability presents an issue in the case of a bad acting user. A service may want to ban a user, but since each showing is unlinkable, they have no way of distinguishing whether a credential belongs to this user. Therefore, a set of pseudonyms are generated from a unique root and signed by the Issuer for each credential. Each pseudonym has a keypair associated with it and each credential showing is signed by the pseudonym's keypair. The user then presents the verifier with the transcript
, transcriptSignature
, pseudonymPublicKey
and certSig
. The certSig
is first validated over the pseudonymPublicKey
against the organisation's key to make sure this pseudonym is certified. Then the transcript signature is validated, and finally the credential showing is validated.
Revocability
A service can report a pseudonymPublicKey
to the Issuer to have them revoked. The Issuer may then determine which identity this pseudonym belongs to and publish the root
to a revocation list associated with the certification. Verifiers interested in this certification subscribe to the revocation list and calculate the pseudonymPublicKey
s associated with the revocation listed user and can check each new user against this list.