Tutorial

Anonymous Credentials

Creating Credentials

The Findora Ledger JavaScript API allows users to create and manage anonymous credentials. Anonymous credentials allow a user to selectively prove information about their identity. In order to create an anonymous credential, a user must have the consent of a credential issuer.

In this example, we will demonstrate how a credential issuer named Bob can issue credentials to a user named Alice. Bob is in the credit business. He is qualified to attest to someone's credit score and yearly income. Alice is looking to take out a loan.

First, we will step into Bob's shoes. Bob must first create a credential template object specifying the two credential attributes he is qualified to sign off on:

const creditScoreAttribute = { name: 'credit_score', size: 3 };
const incomeAttribute = { name: 'income', size: 9 };
const credentialTemplate = [creditScoreAttribute, incomeAttribute];

Each credential attribute has two fields, a name field and a size field. The name field is the human-readable name of the attribute, and the size field is the byte size of the attribute value. Our JavaScript API treats credential values as strings, so the size field is the number of characters in the UTF-8 encoded string representing a credential attribute value. The highest possible credit score is 800, so Bob will reserve 3 bytes for credit score values. Bob will reserve 9 bytes for income values. Now that Bob has defined his credential template he can generate the keys that he will use to issue credentials:

const bobIssuerKeys = Ledger.wasm_credential_issuer_key_gen(credentialTemplate);

Alice must now generate the keys she will use to manage credentials:

const aliceUserKeys = Ledger.wasm_credential_user_key_gen(bobIssuerKeys.get_pk());

Notice that Alice needed Bob's issuer public key to create her key pair. Credential user keys are tied to a single credential issuer key. If Alice wanted to manage another set of credentials issued by different issuer, she would have to generate a second credential user key pair tied to that issuer's public key.

In order for Alice to create an anonymous credential, she needs a signature from Bob attesting to the values of the two credential attributes that Bob specified above. Bob will now generate a credential signature:

const attributeValues = [{ name: 'credit_score', val: '760' }, { name: 'income', val: '000100000' }];
const credentialSignature = Ledger.wasm_credential_sign(bobIssuerKeys.get_sk(), aliceUserKeys.get_pk(), attributeValues);

Note that Bob passed Alice's public key into wasm_credential_sign, linking the signature uniquely to that key. Also note that we padded the income attribute with leading zeroes to ensure that the credential value is 9 bytes long.

With a credential signature, Alice can create her credential:

const aliceCredential = Ledger.create_credential(bobIssuerKeys.get_pk(), credentialSignature, attributeValues);

Selectively Revealing Credential Attributes

Now that Alice has her credential, it is time for her to put it to use. Imagine that Alice wants to take out a loan, and she needs to prove that she has a decent credit score. Alice can use her credential to create a proof that her credit score is 760 without revealing her income. In general, a credential user can reveal a subset of their credential attributes without revealing others. Alice will now generate a selective reveal proof:

const attributesToReveal = ['credit_score']; // Alice will only reveal her credit score
const revealProof = Ledger.wasm_credential_reveal(aliceUserKeys.get_sk(), aliceCredential, attributesToReveal);

Alice can now prove that her credit score is 760 without revealing her income:

const revealValue = [{ name: 'credit_score', val: '760' }];
assert.doesNotThrow(() => Ledger.wasm_credential_verify(bobIssuerKeys.get_pk(), revealValue, revealProof.get_commitment(),
  revealProof.get_pok()));

You may have noticed that the reveal proof consists of a commitment and a proof of knowledge. The commitment is a structure that commits to Alice's credentials, and the proof of knowledge proves that the revealed attributes are consistent with credential underlying the commitment. If Alice tries to claim that her credit score is a perfect 800, wasm_credential_verify fails:

const fakeValue = [{ name: 'credit_score', val: '800' }];
assert.throws(() => Ledger.wasm_credential_verify(bobIssuerKeys.get_pk(), fakeValue, revealProof));

Using the Address Identity Registry

The Address Identity Registry (AIR) allows users to associate credentials with a ledger address. Attaching credentials to the AIR does not reveal the linked ledger address, but is binding. At some point in the future, a user may need to prove that a credential in the AIR is tied to a certain ledger address to acquire assets or prove to validators that the hidden credentials bound to their address satsifies a policy.

Alice will now demonstrate how to commit a credential to the AIR, linking that credential with her ledger key in the process. First, Alice will generate the asset key pair she wishes to associate her credential with:

const PASSWORD = 'findorarocks123';
const keyStore = new KeyStore.KeyStore(PASSWORD);
const masterKey = keyStore.genMasterKey(PASSWORD);
const aliceAssetKeys = keyStore.genKeyPair(masterKey, 'Alice');

Now, Alice can generate a credential commitment that is bound to her asset public key:

const credentialCommitmentData = Ledger.wasm_credential_commit(aliceUserKeys.get_sk(), aliceAssetKeys.get_pk(), aliceCredential);

The object credentialCommitmentData stores three other objects: (1) A binding credential commitment, and (2) a proof that the credential committed to was signed by a credential issuer (Bob in this case), and (3) a credential key that Alice can use to reveal attributes tied to her credential later on.

Now Alice can construct a transaction that, if accepted by the ledger, will attach her credential commitment to the AIR.

const network = new Network.Network(PROTOCOL, HOST, QUERY_PORT, SUBMISSION_PORT, LEDGER_PORT);
const blockCount = BigInt((await network.getStateCommitment())[1]);
const commitment = credentialCommitmentData.get_commitment();
const proof = credentialCommitmentData.get_pok();
const airTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_operation_air_assign(aliceAssetKeys, aliceUserKeys.get_pk(), bobIssuerKeys.get_pk(), commitment, proof).transaction();
await network.submitTransaction(airTxn);

Now that Alice has attached her credential commitment to the AIR, she can verify that is has been committed to the ledger. The process is similiar to verifying a transaction or a UTXO. The verification method checks that the proofs provided by the ledger are consistent with the ledger state commitment:

await Utils.sleep(4);
const airKey = aliceUserKeys.to_json().pk; // AIR commitments are indexed by credential user public key
let airResult = await network.getAIRResult(JSON.stringify(airKey));
airResult = Ledger.AuthenticatedAIRResult.from_json(airResult);
const stateCommitment = await network.getStateCommitment();
assert(airResult.is_valid(JSON.stringify(stateCommitment[0])));

Alice can show that the commitment stored in the AIR is valid:

const airResComm = airResult.get_commitment();
Ledger.wasm_credential_verify_commitment(bobIssuerKeys.get_pk(), airResComm, proof, aliceAssetKeys.get_pk());

Alice can also selectively reveal other attributes and prove that her AIR commitment commits to these attributes:

const newProof = Ledger.wasm_credential_open_commitment(aliceUserKeys.get_sk(), aliceCredential,
  credentialCommitmentData.get_commit_key(), ['income']);
const newRevealValue = [{ name: 'income', val: '000100000' }];
assert.doesNotThrow(() => Ledger.wasm_credential_verify(bobIssuerKeys.get_pk(), newRevealValue, airResComm,
  newProof));