Credentials
As mentioned previously, an ACDC can be more than just a credential as the issuee field is optional. The Signify API was originally built with credential use cases in mind, so it’s due an update to its name as it supports untargeted ACDCs as well!
Registries
The implementation of registries in keripy is well described here . Simply put, ACDCs are issued in registries so that their issuance and revocation status can be controlled in a secure way. These registries determine which identifier or identifiers have control authority over state in the registry.
const result = await client.registries().create({
name: prefix,
registryName: "Alice's registry",
nonce: "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_"
});
const registryPrefix = result.regser.pre; // Registry identifier
This will create a simple registry, and the registry identifier will be anchored in the KEL of the identifier prefix
, which effectively signs the creation of the identifier.
The nonce
is optional, but if you want to create multiple registries with the same controlling identifier it’s needed to ensure each has a unique registry identifier.
The name
parameter accepts the human readable name for your identifier, which will most likely be phased out in favour of passing the prefix in a breaking change down the line.
In the simple registry version, which we will cover here, there is a single controlling identifier which can issue and revoke credentials, and the identifier cannot be changed. Technically, the issuance and revocation events in the registry form their own hashed-linked data structure called a Transaction Event Log, or TEL for short. As expected, TEL events are not signed directly, but rather are anchored in the KEL of the registry controller.
In the most advanced registry types, there are a list of registry controllers called backers, and a threshold. A registry is only valid once it has been anchored in a threshold number of backers, and registry rotation events can be issued to update the backer list.
Issuance
We are now ready to start issuing ACDCs! Every ACDC follows a particular schema, and we will be adding a schema builder UI to our credential management tooling soon. For now, schemas can be hand-crafted and SAIDified using this a tool from this repository .
Once a schema has been created, the $id
(SAID) field of the JSON schema can be referenced when issuing credentials.
Since we always reference using the SAID, there are no security concerns in schemas being updated by attackers.
Let’s issue a credential to our new registry!
const QVI_SCHEMA_SAID = "EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao";
const result = await client.credentials().issue(prefix, {
ri: registryPrefix,
s: QVI_SCHEMA_SAID,
a: {
i: holderPrefix,
LEI: "5493001KJTIIGC8Y1R17"
}
});
The a
field represents the attributes of the credential, and may be nested or contain different value types.
In the case of the QVI credential in GLEIF’s vLEI ecosystem, the only user-facing attribute is the LEI
number.
a.i
is reserved as the issuee field, which may or may not be relevant for your ACDC.
a.dt
will automatically get populated with the current timestamp by the Signify client.
a.d
is also reserved as the SAID of the attributes block.
Sections in ACDCs can be “collapsed” using their SAID for compactness, which enables partial disclosure.
Outer SAIDs, such as the credential identifier account for this and stay the same as they are always calculated in the compact form.
Private credentials
For many credentials, they will contain sensitive private information and require further protection from a privacy point of view.
Adding additional u
fields alongside any calculated SAIDs, such as the top-level credential SAID and the attributes block SAID provides sufficiently entropy to protect from rainbow or dictionary attacks.
We can use the Salter
class in Signify to produce a random salt to achieve this entropy.
import { Salter } from "signify-ts";
const RE_SCHEMA_SAID = "EJxnJdxkHbRw2wVFNe4IUOPLt8fEtg9Sr3WyTjlgKoIb";
const result = await client.credentials().issue(prefix, {
ri: registryPrefix,
s: RE_SCHEMA_SAID,
u: new Salter({}).qb64,
a: {
i: holderPrefix,
attendeeName: "John Smith",
u: new Salter({}).qb64
}
});
const acdcSaid = result.acdc.sad.d;
Edges
In case we want to chain an ACDC to existing ACDCs, we can define the edges at issuance time. Each edge must define the SAID of the ACDC it points to, and the schema.
const result = await client.credentials().issue(prefix, {
a: {
i: legalEntityPrefix,
LEI: "5493001KJTIIGC8Y1R17",
},
ri: registryPrefix,
s: LE_SCHEMA_SAID,
e: Saider.saidify({
d: "",
qvi: {
n: qviCredential.sad.d,
s: qviCredential.sad.s
}
})[1],
});
This creates a LE credential from the vLEI ecosystem, that is linked to a QVI credential using the top-level e
field.
For more information on edges, please see here.
These top-level blocks, such as a
and e
can be compacted, so there must be a SAID within the block.
In Signify, the a
field auto computes and adds the d
field for you.
Other fields such as e
should use the Saider
class to “saidify” the block, which will insert the SAID in the d
field of that block.
d
is the default field designated as the SAID, but this is customizable.
For example, the $id
field is used for schema SAIDs.
Rules
The top-level r
field may be used to embed Ricardian contracts within ACDCs to achieve contractually-protected graduated disclosure.
These rules are both machine and human readable.
const result = await client.credentials().issue(prefix, {
a: {
i: legalEntityPrefix,
LEI: "5493001KJTIIGC8Y1R17",
},
ri: registryPrefix,
s: LE_SCHEMA_SAID,
r: Saider.saidify({
d: "",
usageDisclaimer: {
l: "Usage of a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, does not assert that the Legal Entity is trustworthy, honest, reputable in its business dealings, safe to do business with, or compliant with any laws or that an implied or expressly intended purpose will be fulfilled."
},
issuanceDisclaimer: {
l: "All information in a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, is accurate as of the date the validation process was complete. The vLEI Credential has been issued to the legal entity or person named in the vLEI Credential as the subject; and the qualified vLEI Issuer exercised reasonable care to perform the validation process set forth in the vLEI Ecosystem Governance Framework."
}
})[1],
});
The IPEX Protocol
The issuance in the previous section ensures our Key Event Log contains all the necessary anchored SAIDs of the credential registry and credential itself. It is now verifiable!
The next step is to share that information with the issuee so they can accept it and share with verifiers. For this, we have the IPEX protocol, where IPEX stands for Issuance and Presentation EXchange.
IPEX is a protocol which can be used:
- Between issuer and issuee to negotiate the terms of an issuance
- Between issuee and verifier to negotiate a presentation
Both of these are similar flows, so IPEX is designed around the concept of a discloser that discloses information to a disclosee. The information is always related to credentials.
Your use case might not require IPEX at all! For example, if a company wants to issue their quarterly reports as an ACDC, they can make the ACDC itself available however they want, such as on a web API, instead of disclosing to every verifier individually which would be overkill!
There is a POST /credentials/verify
endpoint in KERIA to import and verify a credential directly.
The corresponding Signify interface is on a forked version right now, please reach out if you require this!
Negotiation
The IPEX protocol is a state machine which can be used to achieve graduated disclosure. On request, the discloser can disclose only the necessary amount of information within an ACDC required to further a business transaction. The disclosee can then decide if they require more information and request more. The resulting protocol protects both discloser and disclosee from leakage of unnecessary information, which may be a liability for the disclosee.
If we combine this with the rules section, it can be considered contractually-protected graduated disclosure, which a disclosee accepts the terms outlined in the rules section before continuing the transaction.
To our knowledge, graduated disclosure still requires more development in keripy. This is on our roadmap!
Issuance flow
The full set of IPEX messages can be used to negotiate the terms of an issuance. For now, we will focus on the simpler scenario where no negotiation is required.
This will only involve 2 IPEX message types, grant
and admit
.
const acdc = await client.credentials().get(acdcSaid);
const [grant, gsigs, gend] = await client.ipex().grant({
senderName: prefix,
acdc: new Serder(acdc.sad),
anc: new Serder(acdc.anc),
iss: new Serder(acdc.iss),
ancAttachment: acdc.ancAttachment,
recipient: holderPrefix,
datetime: new Date().toISOString().replace("Z", "000+00:00")
});
const operation = await client.ipex().submitGrant(prefix, grant, gsigs, gend, [holderPrefix]);
Granting is the act of disclosing (completely, or partially) an ACDC. The holder can now admit the credential to acknowledge they have received it.
As explained here, the holder can check for a new /exn/ipex/grant
notification, and use the exchanges.get()
method to retrieve the message.
const [admit, asigs, aend] = await client.ipex().admit({
senderName: prefix,
grantSaid: grantExn.d,
recipient: issuerPrefix,
datetime: new Date().toISOString().replace("Z", "000+00:00")
});
const operation = await client.ipex().submitAdmit(prefix, admit, asigs, aend, [issuerPrefix]);
As a holder, a credential won’t become available with the credentials().get
or credentials().list
until admitted.
The admit
message is a non-repudiable signature to prove you received it at some point.
If the holder is a group, a threshold number of members must sign the same admit message. Please see this discussion for more insight into group coordination.
Presentation flow
The presentation flow is the same as issuance, except the holder sends a grant
message to a verifier, and the verifier can send back an admit
message as acknowledgement.
There are 3 other IPEX messages which can be used to achieve a presentation request flow: apply
, offer
, agree
.
The general idea is:
- Using an
apply
message, the disclosee will request a presentation of an ACDC with a specific schema SAID, and can optionally specify specific values the ACDC must have. - The discloser will
offer
an ACDC to present, and the disclosee canagree
. - The discloser can now respond to the agree with a
grant
message. - Finally, the disclosee can acknowledge the presentation with an
admit
message.
There are many possible variations of this as it can be modelled as a state machine to continuously request more data.
There is also a spurn
message which indicates rejection to terminate early.
Due to the pending graduated disclosure work, our wallet will disclose the full ACDC at the offer
stage, and once again in the grant
stage.
This is just to keep within the bounds of the protocol and will be improved upon!
These messages work in the same way as the others, so please refer to this Signify integration test for an example of the apply
-> admit
flow.
Just note that:
- IPEX can only be initiated with an
apply
orgrant
message. - Every non-initiating message must link back to the previous message. In the previous code example, the admit contained this field:
grantSaid: grantExn.d
.
These extra messages, apply
, offer
and agree
can also be used to negotiate the terms of a credential issuance.
Revocation
Revocation of a credential is one of the primary use cases for credential registries, and is very simple to do.
const operation = await client.credentials().revoke(prefix, acdcSaid);
As a result, the credential when fetched from KERIA will have a status
field of 1
instead of 0
to indicate revocation.
A holder can be informed of a credential revocation by sending another IPEX grant message.
How an issuer decides to make revocation status available to verifiers is ecosystem and use case dependent. The TEL event in the registry just needs to verified to validate the status change.