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])
}
}