/Wealize-alastria-wallet

Wallet for AlastriaID, contribution by Wealize.

Primary LanguageTypeScriptBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

WEALIZE - Alastria Wallet

Example mobile application to implement Alastria user stories

Getting Started 🚀

Requirements

  • Node.js 10+
  • Yarn / Npm
  • Android Studio / xCode (IOS)
  • openjdk 1.8

Installation 🔧

yarn install

Additional Configuration

Certain variables that are used in the code come from ENVs. this is an example of the ones used.

API_BASE_URL=**** // This variable represents the main address of our backend
NODE_IP=****      // This variable represents the IP of the blockchain node that we are going to use

Run in development

Run app in android

yarn android

Run app in iOS

yarn ios

Run Jest tests

yarn test

Generate Android release app

To generate AAB (recommended way to upload apps to Google Play)

cd android
./gradlew bundleRelease

The app can be found in or

android/app/build/outputs/bundle/release/app-release.aab

To generate APK

cd android
./gradlew assembleRelease

The app can be found in or

android/app/build/outputs/apk/release/app-release.apk

How to use 📚

Create Wallet with DID

In the registration process, the wallet will be created from the PIN code that we want, and a security phrase will be created for its recovery.

To complete this process, it will be necessary to receive the DID from the backend, which we will capture with a QR code with the following structure.

{
  token: '123j21i321i3h2h3ug2gdsuadhshdu1h232131312asd...', // JWT
  subject_id: 1
}

the token will be a jwt in which it will be decoded and will show us the following structure.

// decoded token
{
  header: { alg: 'ES256K', typ: 'JWT' },
  payload: {
    ani: 'redT',
    cbu: 'https://backend.com/api/v1.0/entity/alastria/did/',
    exp: 1,
    gwu: 'http://16.288.25.6/rpc',
    iat: 1634901714,
    iss: 'did:ala:quor:redT:7048482488d304480745f24af546262a409e1773'
  },
  signature: 'I5dyyfDURcSLQ1gwgAS1AFvnITD1qjGwmY6ONB321Dc8PHrwzxsQLcBIRRaZvJEkwrDQaSubbJIcYcE'
}
  • cbu: Is the address to the endpoint used.
  • gwu: Is the address to the node used.
  • iat: Is the date of creation.
  • iss: Is the did of the subject.

The iss that will be the did, will be verified and saved in the wallet.

At the end of the process we will receive a security phrase to be able to change the pin in case of forgetting it.

Alastria Docs

Store Credential

The process of saving a credential will start when we read a QR code, this QR code will have this structure.

{
  id: 59,
  secret: '1e9a88bc-ce57-4f6a-bfce-be5a49d57563',
  cbu: 'https://backend.com/api/v1.0/alastria/get-credential'
}
  • id: Is the id of the credential in the backend
  • secret: This secret is generated by the backend, which will be used to make the subsequent request to the backend.
  • cbu: Is the address to the endpoint used.

This process is done for greater security and to shorten the information that goes inside the QR, since in the case of carrying images it could cause problems when generating said QR.

  • In the event that you do not want to receive the credential through a request to the endpoint, it will be necessary to skip this process and directly decode the JWT.
// This is the process that follows the use of the secret to make a request to the endpoint
const getCredentialWithSecret = async (qrReadEvent: BarCodeReadEvent) => {
  const { cbu, id, secret }: CredentialQrData = JSON.parse(qrReadEvent.data)
  const response = await ApiClient.post(cbu, {
    id,
    secret
  })

  return response.credential
}
// This would be an example of the response of the request to the endpoint
{
  credential: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE2MjcDov...' // JWT
}

This would be an example of the decoded JWT that we receive from the endpoint, which would be the structure of a credential.

// Decoded JWT
{
  header: { alg: 'ES256K', typ: 'JWT' },
  payload: {
    iat: 1622018477,
    iss: 'did:ala:quor:redT:7048482488d304480745f24af546262a409e1773',
    vc: {
      '@context': [
        'https://www.w3.org/2018/credentials/v1',
        'https://alastria.github.io/identity/credentials/v1',
        'http://schema.org/'
      ],
      credentialSubject: {
        subjectInfo: {
          birthDate: '10/02/1849',
          familyName: 'garcia',
          gender: 'male',
          givenName: 'juan',
          identifier: {
            propertyID: 'https://www.wikidata.org/wiki/Q1478365',
            value: '36854698R'
          },
          info_type: 'nie_copy',
          name: 'juan garcia'
        }
      },
      type: [
        'VerifiableCredential',
        'AlastriaVerifiableCredential',
        'Person',
        'PropertyValue'
      ]
    }
  },
  signature: 'Jkh7G-z-C61YThgafEJ3TG3wtFeQV9iZ94X4inS82UxH-9E7KCMYqlLK_yxjXpTCgEcPjp'
}

During the process, the services of alastria will be used to register in the blockchain, as we can see in the following example.

  public static async registerInBlockchain(credentials: string[]) {
    const transactions = await Promise.all(
      credentials.map(async (credential: string) => {
        const credentialHash = await PsmHashService.generate(credential)
        // Is not clear what URI the smart contract expect
        return TransactionService.addSubjectCredential(
          credentialHash,
          'https://url.com'
        )
      })
    )
    await TransactionService.sendTransactions(transactions)
  }

Alastria Docs

Share Presentation

To share a credential we will have to read a presentation of said credential by means of a QR code, this QR code stores a JWT that when decoded will have the following structure.

'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJjYnUiOiJByb2QuaGVyb2...' // JWT
// Decoded JWT
{
  header: { alg: 'ES256K', typ: 'JWT' },
  payload: {
    cbu: 'https://backend.com/api/v1.0/alastria/presentation',
    iat: 1635851970,
    iss: 'did:ala:quor:redT:7048482488d304480745f24af546262a409e1773',
    jti: 'fd6a7f1c-f3a5-4d4e-8836-1a9bbe241526',
    pr: {
      '@context': [
        'https://www.w3.org/2018/credentials/v1',
        'https://alastria.github.io/identity/credentials/v1'
      ],
      data: [
        {
          '@context': [
            'https://www.w3.org/2018/credentials/v1',
            'https://alastria.github.io/identity/credentials/v1'
          ],
          field_name: 'nie_copy',
          levelOfAssurance: 0,
          required: true,
          type: [
            'VerifiablePresentationRequest',
            'AlastriaVerifiablePresentationRequest'
          ]
        }
      ],
      procHash: '',
      procUrl: '',
      type: [
        'VerifiablePresentationRequest',
        'AlastriaVerifiablePresentationRequest'
      ]
    }
  },
  signature: 'Q3KodwBPORaVKfc3ghemGMqQuzYjKCxgk_i5Ml819JR5ZEvHgDdCelZnGtaEehw'
}

When saving the presentation on the device, a "psmHash" will be generated using the JWT received, this "psmHash" will be necessary to later revoke said presentation.

During this process, the alastria service is used to create the registry in blockchain, as shown in the following example. It will also create a record in the backend.

  private static async registerInBlockchain(presentation: string) {
    const decodedPresentation = this.decodePresentation(presentation)
    const psmHash = await PsmHashService.generate(presentation)

    const transaction = TransactionService.addSubjectPresentation(
      psmHash,
      decodedPresentation.payload.vp.procUrl
    )
    await TransactionService.sendTransaction(transaction)
  }

  public static async sendPresentation(presentation: string, url: string) {
    await ApiClient.post(url, {
      presentation
    })
    await this.registerInBlockchain(presentation)
  }

Alastria Docs

Differentiation between Credential / Presentation

To differentiate in the process if the QR is a presentation or a credential, we will check if the payload has the property of pr / vc, these should only appear in the case of presentation "pr" (VerifiablePresentationRequest) and credential "vc" (VerifiableCredential)

const checkPayloadType = (decodedJWT: PresentationRequest, jwtData: string) => {
  if (decodedJWT.payload.pr) {
    pushScreen(componentId, SCREEN.CREDENTIAL_PR_INFO, {
      presentationRequest: decodedJWT.payload
    })
  }
  if (decodedJWT.payload.vc) {
    pushScreen(componentId, SCREEN.CREDENTIAL_SHARE_INFO, {
      credentialInfo: decodedJWT.payload,
      jwtData
    })
  }
}

Revoke Presentation

The process of revoking a presentation consists of two parts.

  • Revoke on blockchain

To revoke in blockchain we will use the "psmHash" that was generated when storing the presentation, this will be used with the alastria service, as we see in the following example

  public static async revokePresentationsInBlockchain(
    presentationsPmHashes: string[]
  ) {
    const transactions = presentationsPmHashes.map((presentationPmHash) => {
      return TransactionService.updateSubjectPresentation(
        presentationPmHash,
        ASK_DELETION_STATUS
      )
    })

    await TransactionService.sendTransactions(transactions)
  }
  • Revoke in the backend

The revocation process will be a request to the endpoint, this being the "cbu" property that we have saved on the device, within this request the generated psmHash will be sent.

  public static async revokePresentationsInBackend(
    presentations: PresentationEntity.default[]
  ) {
    for (const presentation of presentations) {
      await ApiClient.delete(presentation.cbu, {
        psmhash: presentation.psmHash
      })

      await PresentationRepository.deletePresentationsById([presentation.id])
    }
  }

Alastria Docs

Video Example 🎥

Record_2021-11-04-13-28-46_1779284fe2df9b9bb5ea245a3ce3a80d.mp4

Class Diagram

Captura de pantalla 2021-11-08 a las 17 03 06

License

Copyright © 2021, Wealize.

BSD 3-clause