Nutmeg Plundering Muse

    @affinidi/wallet-core-sdk
    TypeScript icon, indicating that this package has built-in type declarations

    6.2.1 • Public • Published

    Affinity Core SDK - Affinity network DID solution

    WARNING Action required from you
    Update your services to use Affinidi SDK v6.0.4 or above.
    Note please pay attention to the changelog while upgrading the version of SDK as some methods may be changed or deprecated. If you are using Affinidi SDK below v6, your application doesn’t support Affinidi Vault and hence we cannot migrate you out of the Bloom Vault.
    With Affinidi SDK v6.0.4 onwards, we have also introduced automatic trigger of migration to Affinidi Vault and that is why we ask you to upgrade to that version or above.
    Otherwise your credentials will never be migrated. The migration will not anyhow impact SDK performance negatively.
    Furthermore, if you have more than 100 credentials in Bloom Vault the performance should be increased after migration.

    Table of contents

    How to install

    npm i --save @affinidi/wallet-core-sdk

    Setup Integration Tests

    Test credentials should be added to the top level .env file. These contain usernames and passwords of pre-populated accounts on the staging environment. Reach out to a team member for instructions on how to set up this file, or to obtain a copy.

    You can also run integration tests against dev:

    TEST_AGAINST=dev npm run test:integration

    Initialize

    Create API-KEY

    You should register your entity at Affinity for appropriate environment staging, production or dev, to obtain the apiKey and apiKeyHash values, one of which should be passed via options as a required parameter.

    If you want to specify issuer's URL, pass it in the options.

    You can also specify the stack environment to be used in env variable. env - (optional) is enum which can be dev | staging | prod (staging is used by default).

    const options = {
      env: 'staging',
      apiKey: 'YOUR API KEY'
    }

    OR

    const options = {
      env: 'staging',
      accessApiKey: 'YOUR API KEY HASH VALUE'
    }

    encryptedSeed- randomly auto generated by Generate seed more detail example material that used as input for a private key generation. Generated as a part of register / signUp process and stored as encrypted value in protected vault. seed itself never exposed to the public and available in encrypted form as a property of wallet.

    const wallet = AffinidiWallet.openWalletByEncryptedSeed(options, encryptedSeed, password)

    Issuer / Holder / Verifier interface examples

    Create a new wallet

    'elem-anchored' did method returns did:elem, and it is anchored with sidetree in ropsten testnet
    note: elem-anchored doesn't support external keys due to Sidetree protocol limitations

    IMPORTANT NOTICE: Please be informed that this Ropsten is a Testnet and an environment deployed for testing purposes. Please do not include sensitive or valuable information during your trials on the Testnet. Ropsten does not guaranty the consistency, stability or uninterrupted access to the functionalities provided herein and before as well as during the testing activities the user is informed and understands that any information and connections introduced, uploaded or generated during your interactions with the Testnet can and will be removed at any time at the sole criteria of Ropsten.

    const options = {
      env: 'staging',
      apiKey: '....',
      didMethod: '....' // 'elem' (default),  'jolo' or 'elem-anchored'
    }
    
    const wallet = await AffinidiWallet.createWallet(options, password)

    Sign DID document

    const signedDidDocument = await wallet.signDidDocument(didDocument)

    Interface

    const { AffinidiWallet } = require('@affinidi/wallet-{PLATFORM}-sdk') // where platform is one of 'browser', 'expo', 'node', 'react-native'

    Create DID

    const wallet = await AffinidiWallet.createWallet(options, password)
    const { did, encryptedSeed } = wallet

    Intiate SDK

    const wallet = AffinidiWallet.openWalletByEncryptedSeed(options, password, encryptedSeed)

    Get DID

    const wallet = AffinidiWallet.openWalletByEncryptedSeed(options, password, encryptedSeed)
    
    const did = wallet.did

    did - user's DID

    Passwordless sign in or sign up if user does not exist + DID creation

    NOTE: This passwordless method was designed for a specific usecase, and its
    simple user interface achieved by making extra calls to AWS Cognito.
    If this method is chosen for authentication you may see failed requests
    to Cognito in the browser's console - that is expected behaviour,
    exceptions are caught and handled properly.
    
    const token = await AffinidiWallet.initiateSignInPasswordless(options, username, messageParameters)

    username - email or phoneNumber, of existing Cognito user or if it does not exist, a new one will be created.

    IMPORTANT: Username is case sensitive, so 2 separate accounts will be created
    on sign up for `Test@gmail.com` and `test@gmail.com`.
    
    In case you want to have a case-agnostic behaviour, please resolve this
    on the application layer by normalizing the input before passing it to the SDK
    (e.g. email.toLowerCase()).
    

    messageParameters - (optional) used to specify message, htmlMessage, subject, see signup method.

    Confirm sign in

    const { isNew, wallet } = await AffinidiWallet.completeSignInPasswordless(token, confirmationCode, options)

    token - from previous step.

    confirmationCode - 6 digits code, generated and sent by AWS Cognito/SES.

    Returns isNew flag, identifying whether new account was created, and initialized instance of SDK - wallet.

    Sign up

    We STRONGLY recommend using a password at least 8 characters, but it's allowed to be 6 min (in this case - salt as username hash and special character will be added on signup, and the same rule will be applied on login cases)

    const wallet = await AffinidiWallet.signUpWithUsername(options, username, password, messageParameters)

    or

    const token = await AffinidiWallet.initiateSignUpByEmail(options, email, password, messageParameters)

    or

    const token = await AffinidiWallet.initiateSignUpByPhone(options, phone, password, messageParameters)

    Password recovery is not possible unless user have registered with email or phone number or have added email or phone number later.

    IMPORTANT: Username is case sensitive, so 2 separate accounts will be created
    on sign up for `Test@gmail.com` and `test@gmail.com`.
    
    In case you want to have a case-agnostic behaviour, please resolve this
    on the application layer by normalizing the input before passing it to the SDK
    (e.g. email.toLowerCase()).
    

    password - optional. Requirements: min length 8, require number, upper and lowercase letter.

    NOTE: password is optional if username is email or phone number only. If not provided, user will be able to login with passwordless flow only (initiateLogInPasswordless + completeLogInPasswordless, with OTP submit). When username is arbitrary username, password must be provided.

    messageParameters - optional

    const htmlMessage = `
      <table align="center" border="1" cellpadding="0" cellspacing="0" width="600">
        <tr>
         <td bgcolor="#70bbd9">
           here is your {{CODE}}.
         </td>
        </tr>
      </table>
    `
    const messageParameters = {
      message: 'Welcome to Affinity, your OTP: {{CODE}}'
      subject?: 'Your verification Code'
      htmlMessage?
    }
    
    {{CODE}} - will be replaced at the message by OTP

    If htmlMessage not provided, meesage parameter will be used

    To finish registration:

    NOTE: email/SMS with verification code (OTP) will be sent to the provided email/phoneNumber, unless username is an arbitrary username.

    const wallet = await AffinidiWallet.completeSignUp(options, token, confirmationCode)

    token - token from the previous step (value returned from the initiateSignUp).

    confirmationCode - OTP sent by AWS Cognito/SES. Parameter is required if email/phoneNumber was given as a username, and is ignored in case when username is an arbitrary username.

    To re-send sign up confirmation code (in case when username is email/phoneNumber):

    await AffinidiWallet.resendSignUpConfirmationCode(options, token, messageParameters)

    token - token returned by initiateSignUp.

    messageParameters - (optional) used to specify message, htmlMessage, subject, see signup method.

    Sign up with email/phoneNumber (example)
    const email = 'great_user@email.com'
    const password = 'Password123'
    const options = { env: 'dev' }
    
    const token = await AffinidiWallet.initiateSignUpByEmail(options, email, password)
    
    // OTP is sent out by Cognito
    const confirmationCode = '123456' // OTP from email/SMS
    
    const wallet = await AffinidiWallet.completeSignUp(options, token, confirmationCode)

    Now user can login

    Sign up with arbitrary username (example)
    const username = 'great_user'
    const password = 'Password123'
    const options = { env: 'dev' }
    
    const wallet = await AffinidiWallet.signUpWithUsername(options, username, password)

    Now user can login with username and pasword

    Sign up to Affinity Wallet with already created DID/keys. (Create User at Affinity Wallet and store there user keys)

    User already have created keys in advance, e.g.

    const { did, encryptedSeed } = await AffinidiWallet.createWallet(options, password)

    Sign up with already created keys:

    const keyParams = { encryptedSeed, password }
    const email = 'example@affinity-project.org'
    const userPassword = 'Password123'
    const options = { env: 'dev' }
    const messageParameters = { message: 'Welcome to Affinity, here is your OTP: {{CODE}}' } //  (optional)
    
    const token = await AffinidiWallet.initiateSignUpByEmail(options, email, userPassword, messageParameters)
    const confirmationCode = '123456'
    const wallet = await AffinidiWallet.completeSignUp(options, token, confirmationCode, keyParams)

    Or, alternatively, if an arbitrary username is used

    const wallet = await AffinidiWallet.signUpWithUsername(options, username, password, keyParams)

    Update Did Document (supported only for jolo method):

    init SDK

    const wallet = await AffinidiWallet.openWalletByEncryptedSeed(options, encryptedSeed, password)
    // OR
    const wallet = await AffinidiWallet.logInWithPassword(options, userName, userPassword)

    Then

    await wallet.updateDidDocument(didDocument)

    where didDocument - its valid signed didDocument

    Initiate instance of SDK with login and pasword

    To initiate instance of wallet using just login and password (when user already signed up at Affinity, and if stored his keys at Affinity Guardian Wallet).

    const wallet = await AffinidiWallet.logInWithPassword(options, username, password)

    options - (optional) used to specify environment stack (dev | staging | prod).

    Initiate instance of SDK with refreshToken

    To initiate instance of wallet using refreshToken.
    To get refreshToken use wallet.serializeSession() (for wallets with cognito, when logged in)

    Take care about refresh token to save it in right place, its lifetime is 30 days. To invalidate tokens please use wallet.logOut() method. The best way to use only access token and re-login user each hour (lifetime of accessToken)

    const wallet = await AffinidiWallet.logInWithRefreshToken(options, refreshToken)

    options - (optional) used to specify environment stack (dev | staging | prod).

    Passwordless login

    Login to the network by username, registered in AWS Cognito.

    const token = await AffinidiWallet.initiateLogInPasswordless(options, login, messageParameters: MessageParameters )

    login - email or phone number, at which confirmation code will be sent.

    messageParameters - (optional) used to specify message, htmlMessage, subject, see signup method.

    Complete login challenge and initiate instance of SDK:

    const wallet = await AffinidiWallet.completeLoginPasswordless(options, token, confirmationCode)

    token - token from the previous step.

    confirmationCode - 6 digits code, generated and sent by AWS Cognito/SES.

    completeLoginPasswordless could return next errors:

    • COR-5 Invalid confirmation OTP code was uses. As part of error payload new session token is passed that should be used for continuation of completing a session with old OTP
    import retry from "async-retry";
    
    let newToken;
    try {
      const wallet = await AffinidiWallet.completeLoginPasswordless(options, token, invalidConfirmationCode)
    } catch (sdkError) {
      if (sdkError.code === 'COR-5') {
        newToken = sdkError.context.newToken
      }
    }
    const wallet = await AffinidiWallet.completeLoginPasswordless(options, newToken, confirmationCode)

    CAUTION for serverside SDK usage you should always use a token returned with error to continue process

    Up to 3 retries are possible. After 3 times new session should be used

    • COR-13 Invalid confirmation OTP code was used 3 times or more. Use a initiateLogInPasswordless call to initiate a new session.
    • COR-17 Confirmation code is expired. Lifetime of confirmation code is around 3 minutes. Use a initiateLogInPasswordless call to initiate a new session

    Password recovery

    NOTE: Password recovery is not possible with arbitrary username.

    const token = await AffinidiWallet.initiateForgotPassword(options, login, messageParameters)

    username - email or phone number, at which confirmation code will be sent.

    messageParameters - (optional) used to specify message, htmlMessage, subject, see signup method.

    Complete change password challenge:

    const wallet = await AffinidiWallet.completeForgotPassword(options, token, confirmationCode)

    token - token returned by initiatePassword

    confirmationCode - 6 digits code, generated and sent by AWS Cognito/SES.

    newPassword - new password for Cognito user.

    Change password

    User have to be logged in to change password. Otherwise use password recovery.

    await wallet.changePassword(oldPassword, newPassword)

    oldPassword - old password.

    newPassword - new password.

    Change username

    const token = await wallet.initiateChangeEmail(newEmail)

    or

    const token = await wallet.initiateChangePhone(newPhone)

    newEmail or newPhone - email address or phone number to be later used to log into cognito

    Complete change username challenge:

    await wallet.completeChangeEmailOrPhone(options, token, confirmationCode)

    token - token returned on the previous step.

    confirmationCode - 6 digits code, generated and sent by AWS Cognito/SES.

    Sign Out

    Signs out current user from all devices.
    It also invalidates all refresh tokens issued to a user.
    The user's current access and Id tokens remain valid until their expiry.
    Access and Id tokens expire one hour after they are issued.

    await wallet.logOut()

    Serialize session

    Returns all active cognito tokens as json string
    { "accessToken": "eyJraWQiOiJHiLCJlxKU...", "idToken": 'eyJraWQiOiJQYTVjZFwvKzVyb...", "refreshToken": 'eyJjdHkiOiJKV1QiLCJ...", "expiresIn": 1655544042964 }

    await wallet.serializeSession()

    Issuer

    Initiate credential offer request

    const jwtOptions = { audienceDid, expiresAt, nonce, callbackUrl }
    
    const credentialOfferToken = await issuerWallet.generateCredentialOfferRequestToken(offeredCredentials, jwtOptions)

    audienceDid (string) - audience of genreated token.

    expiresAt (isoString) - expires of genreated token.

    nonce (string) - nonce/jti of genreated token.

    callbackUrl (string)

    Initiate DID auth

    const jwtOptions = { audienceDid, expiresAt, nonce, callbackUrl }
    
    const authDidRequestToken = await issuerWallet.generateDidAuthRequest(jwtOptions)

    audienceDid (string) - audience of genreated token.

    expiresAt (isoString) - expires of genreated token.

    nonce (string) - nonce/jti of genreated token.

    callbackUrl (string)

    const offeredCredentials = [
      {
        type: 'IssuerCustomCredential',
        renderInfo,
      },
    ]

    renderInfo (optional) where issuer can define how that VC can be represented/shown.

    Example:

    const renderInfo = {
      logo: {
        url: 'https://miro.medium.com/fit/c/240/240/1*jbb5WdcAvaY1uVdCjX1XVg.png',
      },
      background: {
        url: 'https://i.imgur.com/0Mrldei.png',
      },
      text: {
        color: '#05050d',
      },
    }

    credentialOfferToken can be passed to the wallet side, and let Wallet/Holder option to response to this offer if usser want take offered credentials.

    Validate Holder Response on Offer Request

    const { isValid, did, nonce, selectedCredentials } = await issuer.verifyCredentialOfferResponseToken(
      credentialOfferResponseToken,
      credentialOfferRequestToken,
    )

    credentialOfferRequestToken (optional) - using when need check response against request (nonce, audience).

    Validates response token and verify signature, if verification not passed response { isValid: false } if response is valid returns also { issuer, nonce, selectedCredentials }.

    did - it's DID which signed that response.

    Sign multiple credentials

    import { VCV1Unsigned } from '@affinidi/vc-common'
    import { VCSPhonePersonV1, getVCPhonePersonV1Context } from '@affinidi/vc-data'
    import { buildVCV1Unsigned, buildVCV1Skeleton } from '@affinidi/vc-common'
    
    const unsignedCredentials: VCV1Unsigned[] = [
      buildVCV1Unsigned({
        skeleton: buildVCV1Skeleton<VCSPhonePersonV1>({
          id: 'urn:urn-5:...',
          credentialSubject: {
            data: {
              '@type': ['Person', 'PersonE', 'PhonePerson'],
              telephone: '+1 555 555 5555',
            },
          },
          holder: { id: 'did:...:...' },
          type: 'PhoneCredentialPersonV1',
          context: getVCPhonePersonV1Context(),
        }),
        issuanceDate: new Date().toISOString(),
        expirationDate: new Date(new Date().getTime() + 10 * 60 * 1000).toISOString(),
      }),
    ]
    
    const credentials = await signCredentials(credentialOfferResponseToken, unsignedCredentials)

    credentialOfferResponseToken - credential offer response JWT.

    credentialParams - array of params for credentials, where expiresAt is optional.

    Generate signed credential

    import { VCSPhonePersonV1, getVCPhonePersonV1Context } from '@affinidi/vc-data'
    
    const credentialSubject: VCSPhonePersonV1 = {
      data: {
        '@type': ['Person', 'PersonE', 'PhonePerson'],
        telephone: '+1 555 555 5555',
      },
    }
    
    const credentialMetadata = {
      context: [getVCPhonePersonV1Context()],
      name: 'Phone Number',
      type: ['PhoneCredentialPersonV1'],
    }
    
    const credential = await issuer.signCredential(
      credentialSubject,
      credentialMetadata,
      { credentialOfferResponseToken, requesterDid },
      expiresAt,
    )

    credentialSubject - data which should be present in VC according to VC schema, must be a valid VCV1Subject.

    credentialMetadata - schema of credential (should be defined and Issuer and Verifier use the same, so verifier will be able to understand what kind of credential was created by Issuer).

    Revocation

    SDK Support Issuing Revocable Credential based on Revocation List 2020 W3C standard

    Revocation Flow

    Revocation Flow

    issuance of Revocable credential

       const unsignedCredential = buildVCV1Unsigned({
          skeleton: buildVCV1Skeleton<VCSPhonePersonV1>({
            id: `credId:${credId}`,
            credentialSubject: {
              data: {
                '@type': ['Person', 'PersonE', 'PhonePerson'],
                telephone: '+1 555 555 5555',
              },
            },
            holder: { id: holderDid },
            type: 'PhoneCredentialPersonV1',
            context: getVCPhonePersonV1Context(),
          }),
          issuanceDate: new Date().toISOString(),
          expirationDate: new Date(new Date().getTime() + 10 * 60 * 1000).toISOString(),
        })
    
        const revokableUnsignedCredential = await wallet.buildRevocationListStatus(
          unsignedCredential,
          accessToken,
        )

    buildRevocationListStatus will add to unsigned credential special credentialStatus field with revocationlist2020status data.

    Revocation of Revocable credential

     await wallet.revokeCredential(credentialId, 'Status changed', accessToken)
    

    credentialId - id of credential for revoke

    revocation reason - free text reason of revocation

    accessToken

    Verifier

    Initiate Verifiable Presentation request (credential share request)

    Verifiable Presentation according to w3c spec structure flow:
    const jwtOptions = { audienceDid, expiresAt, nonce, callbackUrl }
    
    const presentationChallenge = await verifier.generatePresentationChallenge(
      credentialRequirements,
      issuerDid,
      jwtOptions,
    )

    audienceDid (string) - audience of genreated token.

    expiresAt (isoString) - expires of genreated token.

    nonce (number) - nonce/jti of genreated token.

    callbackUrl (string)

    Generates JWT with info of which VC credentialRequirements to be provided from Wallet/Holder.

    const credentialRequirements = [{ type: ['Credential', 'ProofOfNameCredential'] }]

    callbackUrl - (optional) Holder/Wallet will be able send response on this request to this URL.

    issuerDid - (optional) its contrain, that define required isser of VC.

    credentialShareRequestToken can be send to Wallet/Holder to anwser on this with response with requested VC inside.

    Verifiable Presentation as JWT method:
    const credentialShareRequestToken = await verifier.generateCredentialShareRequestToken(
      credentialRequirements,
      issuerDid,
      options,
    )

    see parameters description at Verifiable Presentation according to w3c spec section

    Validate Verifiable Presentation (Holder Response on Share Request)

    Verifiable Presentation according to w3c spec structure flow:
    const { isValid, did, challenge, suppliedPresentation } = await verifier.verifyPresentation(vp)
    Verifiable Presentation as JWT method:
    const { isValid, did, nonce, suppliedCredentials } = await verifier.verifyCredentialShareResponseToken(
      credentialShareResponseToken,
      credentialShareRequestToken,
      shouldOwn,
    )

    credentialShareResponseToken - (optional) using when need check response against request (when request have constrains).

    shouldOwn - (optional) Verify that subject is holder of VC. Default true as per W3C spec.

    Its validate response token and verify signature on provided VC inside, if verification not passed response { isValid: false }. If response is valid it returns also { did, nonce, suppliedCredentials }.

    Validate Holder Response on Did auth Request

    const { isValid, did, nonce } = await verifier.verifyDidAuthResponse(authDidResponseToken, authDidRequestToken)

    Its validate response token, if verification not passed response { isValid: false } if response is valid returns also { did, nonce }

    Wallet

    Initialize region for storing credentials

    You can specify AWS region where user credentials will be stored using optional storageRegion parameter (region should be a 3 character string correlating to an Alpha-3 country code).

    const options = {
      storageRegion: 'SGP'
    }

    Create Verifiable Presentation (Response on credential share request)

    Verifiable Presentation according to w3c spec structure flow:
    const vp = await wallet.createPresentationFromChallenge(
      presentationChallenge,
      credentials,
      domain,
    )

    credentials - credentials which Holder providing for Verifier.

    callbackURL - (optional)

    domain - (could be empty string)

    Verifiable Presentation as JWT method:
    const responseToken = await wallet.createCredentialShareResponseToken(
      credentialShareRequestToken,
      suppliedCredentials,
    )

    suppliedCredentials - credentials which Holder providing for Verifier.

    Create Response on credential offer request

    const responseToken = await wallet.createCredentialOfferResponseToken(credentialOfferRequestToken)

    Agree to recieve proposed credentials by the Issuer.

    Create Response on DID auth request

    const authDidResponseToken = await wallet.createDidAuthResponse(authDidRequestToken)

    Encrypted messages

    Create encrypted message

    const encryptedMessage = await wallet.createEncryptedMessage(toDid, object)

    toDid - DID, string value of document to be resolved.

    object - value to be encrypted by public key.

    Read encrypted message

    const message = await wallet.readEncryptedMessage(encryptedMessage)

    encryptedMessage - message to be decrypted.

    Credentials vault

    const credentials = [ signedCredential ]
    const storageRegion = 'SGP'
    
    await wallet.saveCredentials(credentials, storageRegion)

    credentials - array of credentials to store in the vault.

    storageRegion - (optional) AWS region where user's credentials will be stored. Region should be a 3 character string correlating to an Alpha-3 country code.

    Get all credentials matching the shareRequestToken

    const credentials = await wallet.getCredentials(shareRequestToken)

    shareRequestToken - optional parameter (if passed - returns VC, which match the request, if not - then returns all VCs).

    Get all credentials

    const credentials = await wallet.getCredentials(null)

    Get a single credential

    const credential = await wallet.getCredentialById(credentialId)

    Delete credential by ID

    await wallet.deleteCredential(credentialId)

    Affinidi Infra dependencies

    This SDK using next Affinidi services:

    • affinidi registry (to anchor when applicable, resolve and update did/didDocument)
    • affinidi verifier (to build credential request)
    • affinidi issuer (to build credential offer and verify credential offer response)
    • affinidi wallet backend (to store endrypted seed and encrypted VC optioanlly as backup)
    • affinidi user management (using only when backup option for encrypted seed used)

    Install

    npm i @affinidi/wallet-core-sdk

    DownloadsWeekly Downloads

    1,178

    Version

    6.2.1

    License

    ISC

    Unpacked Size

    2.92 MB

    Total Files

    324

    Last publish

    Collaborators

    • robert-affinidi
    • standemchuk