Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.
/ portablegabi Public archive

Revocable, anonymous credentials. Typescript API for WASM wrapper of Go library Gabi.

License

Notifications You must be signed in to change notification settings

KILTprotocol/portablegabi

Repository files navigation

Test NPM NPM size Last Commit License

Portablegabi

This TypeScript module of Portable Gabi enables the use of Idemix attribute based anonymous credentials via NPM. It is based on the Gabi Go implementation by the Privacy By Design Foundation but does not use the same API.

A user (in the following referred to as claimer) claiming something in JSON format (e.g. citizenship in a specific country, ownership of a valid driver's license) can request an attestation from a trusted entity (attester) and present these claims to multiple verifiers without revealing their identity or sensitive information (multi-show unlinkability). The claimer can chose which attributes of the claim to disclose to a verifier (selective disclosure).

Tutorial

We recommend visiting our Portablegabi tutorial to better understand how to use our anonymous credentials and the API.

Portablegabi also provides a generated API reference.

Revocation and Substrate

This module can be used with and without a Substrate-based blockchain. However, it was designed to be used with a chain acting as a decentralised storage of each attester's accumulator versions to enable revocation. All processes tied to chain activity can be found in files with chain suffix. In order to use them, you are required to have an active Substrate blockchain implementing our portablegabi-pallet. We also provide a blockchain template which includes our pallet and can be used to store accumulators. In order to use that, just clone and set up the portablegabi-node or use the template to create your own project. Please see our tutorial for more information.

Installing

npm

npm i @kiltprotocol/portablegabi

yarn

yarn add @kiltprotocol/portablegabi

Building + Testing

If you want to help develop portablegabi, we would be glad to merge your pull request. But first, you need to set up a development environment for our project. See our tutorial for more information.

Run in the browser

It is also possible to use the Portablegabi API in the browser, see here for more details.

Example

Please see the example files for a showcase of on- and off-chain usage with single or combined credentials.

const portablegabi = require('@kiltprotocol/portablegabi')
async function exec() {
  /* (1) Claimer Setup */

  // (1.1) Example claim
  const claim = {
    contents: {
      name: 'Jasper',
      age: '42',
      city: 'Berlin',
      id: 'ed638ndke92902n29',
    },
  }

  // (1.2) Create the claimer identity (either from scratch or mnemonic seed).
  const claimer = await portablegabi.Claimer.create()

  /* (2) Attester Setup */

  // (2.1) Create a key pair and attester entity.
  const attester = await portablegabi.Attester.create() // takes very long due to finding safe prime numbers (~10-20 minutes)

  // (2.1.b) Alternatively, use a pre-compiled key pair from /docs/examples/exampleReadme.js
  // const attester = new portablegabi.Attester(pubKey, privKey);
  console.log('Public key:\n\t', attester.privateKey.toString())
  console.log('Private key:\n\t', attester.privateKey.toString())

  // (2.1) Create accumulator (for revocation)
  const accumulator = await attester.createAccumulator()

  /* (3) Attestation */

  // (3.1) Attester sends two nonces to claimer
  const {
    message: startAttestationMsg,
    session: attestationSession,
  } = await attester.startAttestation()

  // (3.2) Claimer requests attestation
  const {
    message: attestationRequest,
    session: claimerSession,
  } = await claimer.requestAttestation({
    startAttestationMsg,
    claim,
    attesterPubKey: attester.publicKey,
  })
  console.log('Claimer requests attestation:\n\t', attestationRequest)

  // (3.3) Attester issues requested attestation and generates a witness which can be used to revoke the attestation
  // the attester might want to inspect the attributes he is about to sign
  const checkClaim = attestationRequest.getClaim()
  console.log('Attester checks claim :\n\t', checkClaim)

  const { attestation, witness } = await attester.issueAttestation({
    attestationSession,
    attestationRequest,
    accumulator,
  })
  console.log('Attester issues attestion:\n\t', attestation)

  // (3.4) Claimer builds credential from attester's signature
  const credential = await claimer.buildCredential({
    claimerSession,
    attestation,
  })
  console.log('Claimer builds credential:\n\t', credential)

  /* (4) Verification */

  // (4.1) Verifier sends two nonces to claimer
  const {
    session: verifierSession,
    message: presentationReq,
  } = await portablegabi.Verifier.requestPresentation({
    requestedAttributes: ['contents.age', 'contents.city'],
    reqUpdatedAfter: new Date(), // request that the nonrevocation proof contains an accumulator which was created after this date or that the accumulator is the newest available
  })
  console.log('Verifier starts verification session:\n\t', presentationReq)

  // (4.2) Claimer reveals attributes
  const proof = await claimer.buildPresentation({
    credential,
    presentationReq,
    attesterPubKey: attester.publicKey,
  })
  console.log('Claimer builds zk-proof on requested attributes:\n\t', proof)

  // (4.3) Verifier verifies attributes
  const {
    verified,
    claim: verifiedClaim,
  } = await portablegabi.Verifier.verifyPresentation({
    proof,
    verifierSession,
    attesterPubKey: attester.publicKey,
    latestAccumulator: accumulator, // the newest available accumulator
  })
  console.log('Verifier verifiers proof:\n\t', verified, verifiedClaim)

  /* (5) Revocation */

  // Revoke the witness of a credential.
  const accumulatorAfterRevocation = await attester.revokeAttestation({
    accumulator,
    witnesses: [witness],
  })

  // Expect failure here due to prior revocation.
  await credential
    .updateSingle({
      attesterPubKey: attester.publicKey,
      accumulator: accumulatorAfterRevocation,
    })
    .catch((e) => {
      if (e.message.includes('revoked')) {
        console.log('Credential was revoked and cannot be updated')
      } else throw e
    })
}
exec()

Troubleshooting

Node process did not exit automatically

Note that due to the usage of a Go WASM via callbacks, all functions are asynchronous. It can happen that NodeJS does not exit automatically since we keep the WASM instance open. We recommend calling goWasmClose() from wasm_exec_wrapper at the end of your process.

Go version below 1.14.1

  [LinkError: WebAssembly Instantiation: Import #3 module="go" function="runtime.nanotime" error: function import requires a callable]
  (node:6909) UnhandledPromiseRejectionWarning: Error: Function genKey missing in WASM

Please check your Go version, it should be at least 1.14.1. If this is the case for you, and you still encounter this problem without having modified our code, please open a ticket.

Limitations

  • all numbers inside a claim are handled as float64
  • arrays are handled as a single attribute. Disclosing a value inside an array is only possible if the whole array is disclosed.