did-jwt
Version: 
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
1,261 lines (1,145 loc) • 54.5 kB
text/typescript
import { jest, describe, expect, it } from '@jest/globals'
import { base64ToBytes, bytesToBase64url, decodeBase64url, hexToBytes } from '../util.js'
import type { Resolvable, VerificationMethod } from 'did-resolver'
import { TokenVerifier } from 'jsontokens'
import MockDate from 'mockdate'
import { getAddress } from '@ethersproject/address'
import {
  createJWS,
  createJWT,
  decodeJWT,
  NBF_SKEW,
  resolveAuthenticator,
  SELF_ISSUED_V0_1,
  SELF_ISSUED_V2,
  verifyJWS,
  verifyJWT,
} from '../JWT.js'
import { EdDSASigner } from '../signers/EdDSASigner.js'
import { ES256KSigner } from '../signers/ES256KSigner.js'
// add declarations for ES256 Tests
import { ES256Signer } from '../signers/ES256Signer'
// @ts-ignore
import jwt from 'jsonwebtoken'
// @ts-ignore
import jwkToPem from 'jwk-to-pem'
const NOW = 1485321133
MockDate.set(NOW * 1000 + 123)
const audAddress = '0x20c769ec9c0996ba7737a4826c2aaff00b1b2040'
const aud = `did:ethr:${audAddress}`
const address = '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74'
const did = `did:ethr:${address}`
const alg = 'ES256K'
const privateKey = '278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f'
const publicKey = '03fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea5358479'
const verifier = new TokenVerifier(alg, publicKey)
const signer = ES256KSigner(hexToBytes(privateKey))
const recoverySigner = ES256KSigner(hexToBytes(privateKey), true)
const publicKeyJwk = {
  crv: 'secp256k1',
  kty: 'EC',
  x: '_dV63sPUOOojf-RrM-4eAW7aa1hcPifqZmhsLqU1hHk',
  y: 'Rjk_gUUlLupor-Z-KHs-2bMWhbpsOwAGCnO5sSQtaPc',
}
const didDocLegacy = {
  '@context': 'https://w3id.org/did/v1',
  id: did,
  publicKey: [
    {
      id: `${did}#keys-1`,
      type: 'Secp256k1VerificationKey2018',
      owner: did,
      publicKeyHex: publicKey,
    },
  ],
  authentication: [
    {
      type: 'Secp256k1SignatureAuthentication2018',
      publicKey: `${did}#keys-1`,
    },
  ],
}
const didDocJwk = {
  '@context': 'https://w3id.org/did/v1',
  id: did,
  verificationMethod: [
    {
      id: `#keys-1`,
      type: 'EcdsaSecp256k1VerificationKey2019',
      controller: did,
      publicKeyJwk,
    },
  ],
  authentication: [`#keys-1`],
}
const didDoc = {
  didDocument: {
    '@context': 'https://w3id.org/did/v1',
    id: did,
    verificationMethod: [
      {
        id: `${did}#keys-1`,
        type: 'EcdsaSecp256k1VerificationKey2019',
        controller: did,
        publicKeyHex: publicKey,
      },
    ],
    authentication: [`${did}#keys-1`],
    assertionMethod: [`${did}#keys-1`],
    capabilityInvocation: [`${did}#keys-1`],
    capabilityDelegation: [`${did}#some-key-that-does-not-exist`],
  },
}
const audDidDoc = {
  didDocument: {
    '@context': 'https://w3id.org/did/v1',
    id: aud,
    verificationMethod: [
      {
        id: `${aud}#keys-1`,
        type: 'EcdsaSecp256k1VerificationKey2019',
        controller: did,
        publicKeyHex: publicKey,
      },
    ],
    authentication: [`${aud}#keys-1`],
    assertionMethod: [`${aud}#keys-1`],
    capabilityInvocation: [`${aud}#keys-1`],
    capabilityDelegation: [`${aud}#some-key-that-does-not-exist`],
  },
}
describe('createJWT()', () => {
  describe('ES256', () => {
    const privateKey = '736f625c9dda78a94bb16840c82779bb7bc18014b8ede52f0f03429902fc4ba8'
    const publicKey_x = '14c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
    const publicKey_y = 'ec41869995bd661849414c523c7dff9a96f1c8dbc2e5e78172118f91c7199869'
    // construct did:key for secp256r1 (unlike did for secp256k1 which is for an Ethereum Address)
    // This originally was constructed by `encodeDIDfromHextString` imported from `did-key-creator`
    // package, but that dependency was removed, so now `did` is just hardcoded
    // const multicodecName = 'p256-pub';
    // const publicKey = '0314c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
    // const did = encodeDIDfromHexString(multicodecName,publicKey)
    const did = 'did:key:zDnaej4NHntda4rNW4FBUJgFzdcgEAXKGRVGE8LuVfRbuMuc1'
    const signer = ES256Signer(hexToBytes(privateKey))
    // verifyTokenFormAndValidity
    function verifyTokenFormAndValidity(token: string, pemPublic: string): boolean {
      let result
      try {
        jwt.verify(token, pemPublic)
        result = true
      } catch (e: any) {
        console.error(e.name + ': ' + e.message)
        result = false
      }
      return result
    }
    // input public key in hex, and export pem
    function publicToJWK(
      publicPointHex_x: string,
      publicPointHex_y: string,
      kty_value: string,
      crv_value: string
    ): JsonWebKey {
      if (publicPointHex_x.length % 2 != 0) {
        publicPointHex_x = '0' + publicPointHex_x
      }
      if (publicPointHex_y.length % 2 != 0) {
        publicPointHex_y = '0' + publicPointHex_y
      }
      const publicPointUint8_x = hexToBytes(publicPointHex_x)
      const publicPointBase64URL_x = bytesToBase64url(publicPointUint8_x)
      const publicPointUint8_y = hexToBytes(publicPointHex_y)
      const publicPointBase64URL_y = bytesToBase64url(publicPointUint8_y)
      return {
        kty: kty_value,
        crv: crv_value,
        x: publicPointBase64URL_x,
        y: publicPointBase64URL_y,
      }
    }
    it('creates a valid JWT', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
      const pemPublic = jwkToPem(publicToJWK(publicKey_x, publicKey_y, 'EC', 'P-256') as any)
      expect(verifyTokenFormAndValidity(jwt, pemPublic)).toBe(true)
    })
    it('creates a valid JWT using a MNID', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer }, { alg: 'ES256' })
      const pemPublic = jwkToPem(publicToJWK(publicKey_x, publicKey_y, 'EC', 'P-256') as any)
      expect(verifyTokenFormAndValidity(jwt, pemPublic)).toBe(true)
    })
    it('creates a JWT with correct format', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
      return expect(decodeJWT(jwt)).toMatchSnapshot()
    })
    it('creates a JWT with correct legacy format', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer }, { alg: 'ES256' })
      return expect(decodeJWT(jwt)).toMatchSnapshot()
    })
    it('creates a JWT with expiry in 10000 seconds', async () => {
      expect.assertions(1)
      const jwt = await createJWT(
        {
          requested: ['name', 'phone'],
          nbf: Math.floor(new Date().getTime() / 1000),
        },
        { issuer: did, signer, expiresIn: 10000 },
        { alg: 'ES256' }
      )
      const { payload } = decodeJWT(jwt)
      return expect(payload.exp).toEqual(payload.nbf!! + 10000)
    })
    it('Uses iat if nbf is not defined but expiresIn is included', async () => {
      expect.assertions(1)
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, expiresIn: 10000 }, { alg: 'ES256' })
      )
      return expect(payload.exp).toEqual(payload.iat!! + 10000)
    })
    it('sets iat to the current time by default', async () => {
      expect.assertions(1)
      const timestamp = Math.floor(Date.now() / 1000)
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
      )
      return expect(payload.iat).toEqual(timestamp)
    })
    it('sets iat to the value passed in payload', async () => {
      expect.assertions(1)
      const timestamp = 2000000
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'], iat: timestamp }, { issuer: did, signer }, { alg: 'ES256' })
      )
      return expect(payload.iat).toEqual(timestamp)
    })
    it('does not set iat if value in payload is undefined', async () => {
      expect.assertions(1)
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'], iat: undefined }, { issuer: did, signer }, { alg: 'ES256' })
      )
      return expect(payload.iat).toBeUndefined()
    })
    it('throws an error if unsupported algorithm is passed in', async () => {
      expect.assertions(1)
      await expect(
        createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, alg: 'BADALGO' })
      ).rejects.toThrowError('Unsupported algorithm BADALGO')
    })
  })
})
describe('createJWT()', () => {
  describe('ES256K', () => {
    it('creates a valid JWT', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer })
      expect(verifier.verify(jwt)).toBe(true)
    })
    it('creates a valid JWT using a MNID', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer })
      expect(verifier.verify(jwt)).toBe(true)
    })
    it('creates a JWT with correct format', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer })
      return expect(decodeJWT(jwt)).toMatchSnapshot()
    })
    it('creates a JWT with correct legacy format', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer })
      return expect(decodeJWT(jwt)).toMatchSnapshot()
    })
    it('creates a JWT with expiry in 10000 seconds', async () => {
      expect.assertions(1)
      const jwt = await createJWT(
        {
          requested: ['name', 'phone'],
          nbf: Math.floor(new Date().getTime() / 1000),
        },
        { issuer: did, signer, expiresIn: 10000 }
      )
      const { payload } = decodeJWT(jwt)
      return expect(payload.exp).toEqual(payload.nbf!! + 10000)
    })
    it('Uses iat if nbf is not defined but expiresIn is included', async () => {
      expect.assertions(1)
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, expiresIn: 10000 })
      )
      return expect(payload.exp).toEqual(payload.iat!! + 10000)
    })
    it('sets iat to the current time by default', async () => {
      expect.assertions(1)
      const timestamp = Math.floor(Date.now() / 1000)
      const { payload } = decodeJWT(await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }))
      return expect(payload.iat).toEqual(timestamp)
    })
    it('sets iat to the value passed in payload', async () => {
      expect.assertions(1)
      const timestamp = 2000000
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'], iat: timestamp }, { issuer: did, signer })
      )
      return expect(payload.iat).toEqual(timestamp)
    })
    it('does not set iat if value in payload is undefined', async () => {
      expect.assertions(1)
      const { payload } = decodeJWT(
        await createJWT({ requested: ['name', 'phone'], iat: undefined }, { issuer: did, signer })
      )
      return expect(payload.iat).toBeUndefined()
    })
    it('throws an error if unsupported algorithm is passed in', async () => {
      expect.assertions(1)
      await expect(
        createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, alg: 'BADALGO' })
      ).rejects.toThrowError('Unsupported algorithm BADALGO')
    })
  })
  describe('Ed25519', () => {
    const ed25519PrivateKey = 'nlXR4aofRVuLqtn9+XVQNlX4s1nVQvp+TOhBBtYls1IG+sHyIkDP/WN+rWZHGIQp+v2pyct+rkM4asF/YRFQdQ=='
    const did = 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU'
    const signer = EdDSASigner(base64ToBytes(ed25519PrivateKey))
    const alg = 'Ed25519'
    const resolver = {
      resolve: jest.fn().mockReturnValue({
        didDocumentMetadata: {},
        didResolutionMetadata: {},
        didDocument: {
          id: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
          publicKey: [
            {
              id: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU#key1',
              type: 'ED25519SignatureVerification',
              owner: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
              publicKeyBase64: 'BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=',
            },
          ],
          authentication: [],
        },
      }),
    } as Resolvable
    it('creates a valid JWT with did:nacl issuer', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { alg, issuer: did, signer })
      const { payload } = await verifyJWT(jwt, { resolver })
      expect(payload).toEqual({
        iat: 1485321133,
        iss: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
        requested: ['name', 'phone'],
      })
    })
    it('can create a jwt in the default non-canonical way', async () => {
      expect.assertions(1)
      // Same payload, slightly different ordering
      const jwtA = await createJWT(
        { reason: 'verification', requested: ['name', 'phone'] },
        { alg, issuer: did, signer }
      )
      const jwtB = await createJWT(
        { requested: ['name', 'phone'], reason: 'verification' },
        { alg, issuer: did, signer }
      )
      expect(jwtA).not.toEqual(jwtB)
    })
    it('can create a jwt in a canonical way', async () => {
      expect.assertions(1)
      // Same payload, slightly different ordering
      const jwtA = await createJWT(
        { reason: 'verification', requested: ['name', 'phone'] },
        { alg, issuer: did, signer, canonicalize: true }
      )
      const jwtB = await createJWT(
        { requested: ['name', 'phone'], reason: 'verification' },
        { alg, issuer: did, signer, canonicalize: true }
      )
      expect(jwtA).toEqual(jwtB)
    })
    it('creates a JWT with correct format', async () => {
      expect.assertions(1)
      const jwt = await createJWT({ requested: ['name', 'phone'] }, { alg, issuer: did, signer })
      return expect(decodeJWT(jwt)).toMatchSnapshot()
    })
    it('creates a JWT with expiry in 10000 seconds', async () => {
      expect.assertions(1)
      const jwt = await createJWT(
        {
          requested: ['name', 'phone'],
          nbf: Math.floor(new Date().getTime() / 1000),
        },
        { alg, issuer: did, signer, expiresIn: 10000 }
      )
      const { payload } = decodeJWT(jwt)
      return expect(payload.exp).toEqual(payload.nbf!! + 10000)
    })
  })
})
describe('verifyJWT() for ES256', () => {
  // const privateKey = '736f625c9dda78a94bb16840c82779bb7bc18014b8ede52f0f03429902fc4ba8'
  const publicKey = '0314c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
  const did = 'did:key:zDnaej4NHntda4rNW4FBUJgFzdcgEAXKGRVGE8LuVfRbuMuc1'
  const didDoc = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
      verificationMethod: [
        {
          id: `${did}#keys-1`,
          type: 'EcdsaSecp256r1VerificationKey2019',
          controller: did,
          publicKeyHex: publicKey,
        },
      ],
      authentication: [`${did}#keys-1`],
      assertionMethod: [`${did}#keys-1`],
      capabilityInvocation: [`${did}#keys-1`],
      capabilityDelegation: [`${did}#some-key-that-does-not-exist`],
    },
  }
  const resolver = {
    resolve: jest.fn(async (didUrl: string) => {
      if (didUrl.includes(did)) {
        return {
          didDocument: didDoc.didDocument,
          didDocumentMetadata: {},
          didResolutionMetadata: { contentType: 'application/did+ld+json' },
        }
      }
      return {
        didDocument: null,
        didDocumentMetadata: {},
        didResolutionMetadata: {
          error: 'notFound',
          message: 'resolver_error: DID document not found',
        },
      }
    }),
  } as Resolvable
  describe('pregenerated JWT', () => {
    const incomingJwt =
      'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOmtleTp6RG5hZWo0TkhudGRhNHJOVzRGQlVKZ0Z6ZGNnRUFYS0dSVkdFOEx1VmZSYnVNdWMxIn0.aMYFY0jitx2Bq9_wGBhEeIyVvzr2XkouCyEP662P8TbAPTpXOC3UrGQONaPD7wleLrMhGdvfod7idSxKXLl64Q'
    it('verifies the JWT and return correct payload', async () => {
      expect.assertions(1)
      const { payload } = await verifyJWT(incomingJwt, { resolver })
      return expect(payload).toMatchSnapshot()
    })
    it('verifies the JWT and return correct profile', async () => {
      expect.assertions(1)
      const {
        didResolutionResult: { didDocument },
      } = await verifyJWT(incomingJwt, { resolver })
      return expect(didDocument).toEqual(didDoc.didDocument)
    })
    it('verifies the JWT and return correct did for the iss', async () => {
      expect.assertions(1)
      const { issuer } = await verifyJWT(incomingJwt, { resolver })
      return expect(issuer).toEqual(did)
    })
    it('verifies the JWT and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring authentication and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, auth: true })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring authentication proofPurpose and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'authentication' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'assertionMethod' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring capabilityInvocation and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityInvocation' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('rejects the JWT requiring capabilityDelegation when not present in document', async () => {
      expect.assertions(1)
      await expect(() =>
        verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityDelegation' })
      ).rejects.toThrowError(
        `DID document for ${did} does not have public keys suitable for ES256 with capabilityDelegation purpose`
      )
    })
    it('rejects the JWT requiring unknown proofPurpose', async () => {
      expect.assertions(1)
      await expect(() => verifyJWT(incomingJwt, { resolver, proofPurpose: 'impossible' as any })).rejects.toThrowError(
        `DID document for ${did} does not have public keys suitable for ES256 with impossible purpose`
      )
    })
  })
})
describe('verifyJWT() for ES256K', () => {
  const resolver = {
    resolve: jest.fn(async (didUrl: string) => {
      if (didUrl.includes(did)) {
        return {
          didDocument: didDoc.didDocument,
          didDocumentMetadata: {},
          didResolutionMetadata: { contentType: 'application/did+ld+json' },
        }
      }
      if (didUrl.includes(aud)) {
        return {
          didDocument: audDidDoc.didDocument,
          didDocumentMetadata: {},
          didResolutionMetadata: { contentType: 'application/did+ld+json' },
        }
      }
      return {
        didDocument: null,
        didDocumentMetadata: {},
        didResolutionMetadata: {
          error: 'notFound',
          message: 'resolver_error: DID document not found',
        },
      }
    }),
  } as Resolvable
  describe('pregenerated JWT', () => {
    const incomingJwt =
      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.tU96omPNxCfQoEADOpLywXUDCMjKXOfTaG61EZwmfvHJrDFQhNbSDzCP2Pe7WdXySosTCuI1T-IQ6SddcWuj_A'
    it('verifies the JWT and return correct payload', async () => {
      expect.assertions(1)
      const { payload } = await verifyJWT(incomingJwt, { resolver })
      return expect(payload).toMatchSnapshot()
    })
    it('verifies the JWT and return correct profile', async () => {
      expect.assertions(1)
      const {
        didResolutionResult: { didDocument },
      } = await verifyJWT(incomingJwt, { resolver })
      return expect(didDocument).toEqual(didDoc.didDocument)
    })
    it('verifies the JWT and return correct did for the iss', async () => {
      expect.assertions(1)
      const { issuer } = await verifyJWT(incomingJwt, { resolver })
      return expect(issuer).toEqual(did)
    })
    it('verifies the JWT and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring authentication and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, auth: true })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring authentication proofPurpose and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'authentication' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'assertionMethod' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('verifies the JWT requiring capabilityInvocation and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityInvocation' })
      return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
    })
    it('rejects the JWT requiring capabilityDelegation when not present in document', async () => {
      expect.assertions(1)
      await expect(() =>
        verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityDelegation' })
      ).rejects.toThrowError(
        `DID document for ${did} does not have public keys suitable for ES256K with capabilityDelegation purpose`
      )
    })
    it('rejects the JWT requiring unknown proofPurpose', async () => {
      expect.assertions(1)
      await expect(() => verifyJWT(incomingJwt, { resolver, proofPurpose: 'impossible' as any })).rejects.toThrowError(
        `DID document for ${did} does not have public keys suitable for ES256K with impossible purpose`
      )
    })
  })
  describe('pregenerated JWT with publicKeyJwk in DID doc', () => {
    const incomingJwt =
      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4OTBlNDVkNzViZDEyNDZlMDkyNDg3MjAxODY0N2RiYTk5NmE4ZTdiOSIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.KIG2zUO8Quf3ucb9jIncZ1CmH0v-fAZlsKvesfsd9x4RzU0qrvinVd9d30DOeZOwdwEdXkET_wuPoOECwU0IKA'
    const jwkResolver = { resolve: jest.fn().mockReturnValue(didDocJwk) } as Resolvable
    it('verifies the JWT and return correct payload', async () => {
      expect.assertions(1)
      const { payload } = await verifyJWT(incomingJwt, { resolver: jwkResolver })
      return expect(payload).toMatchObject({
        iat: 1485321133,
        iss: 'did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9',
        requested: ['name', 'phone'],
      })
    })
    it('verifies the JWT and return correct payload when using assertionMethod', async () => {
      expect.assertions(1)
      const { payload } = await verifyJWT(incomingJwt, { resolver: jwkResolver, proofPurpose: 'assertionMethod' })
      return expect(payload).toMatchObject({
        iat: 1485321133,
        iss: 'did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9',
        requested: ['name', 'phone'],
      })
    })
  })
  describe('pregenerated JWT with legacy resolver', () => {
    const incomingJwt =
      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4OTBlNDVkNzViZDEyNDZlMDkyNDg3MjAxODY0N2RiYTk5NmE4ZTdiOSIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.KIG2zUO8Quf3ucb9jIncZ1CmH0v-fAZlsKvesfsd9x4RzU0qrvinVd9d30DOeZOwdwEdXkET_wuPoOECwU0IKA'
    const legacyResolver = { resolve: jest.fn().mockReturnValue(didDocLegacy) } as Resolvable
    it('verifies the JWT and return correct payload', async () => {
      expect.assertions(1)
      const { payload } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
      return expect(payload).toMatchSnapshot()
    })
    it('verifies the JWT and return correct profile', async () => {
      expect.assertions(1)
      const {
        didResolutionResult: { didDocument },
      } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
      return expect(didDocument).toEqual(didDocLegacy)
    })
    it('verifies the JWT and return correct did for the iss', async () => {
      expect.assertions(1)
      const { issuer } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
      return expect(issuer).toEqual('did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9')
    })
    it('verifies the JWT and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
      return expect(signer).toEqual(didDocLegacy.publicKey[0])
    })
    it('verifies the JWT requiring authentication and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver, auth: true })
      return expect(signer).toEqual(didDocLegacy.publicKey[0])
    })
    it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
      expect.assertions(1)
      const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver, proofPurpose: 'assertionMethod' })
      return expect(signer).toEqual(didDocLegacy.publicKey[0])
    })
  })
  describe('badJwt', () => {
    const badJwt =
      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4MjBjNzY5ZWM5YzA5OTZiYTc3MzdhNDgyNmMyYWFmZjAwYjFiMjA0MCIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.TTpuw77fUbd_AY3GJcCumd6F6hxnkskMDJYNpJlI2DQi5MKKudXya9NlyM9e8-KFgTLe-WnXgq9EjWLvjpdiXA'
    it('rejects a JWT with bad signature', async () => {
      expect.assertions(1)
      await expect(verifyJWT(badJwt, { resolver })).rejects.toThrowError(
        /invalid_signature: no matching public key found/
      )
    })
  })
  describe('validFrom timestamp', () => {
    it('passes when nbf is in the past', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.FUasGkOYqGVxQ7S-QQvh4abGO6Dwr961UjjOxtRTyUDnl6q6ElqHqAK-WMDTmOir21pFPKLYZMtLZ4LTLpm3cQ'
      // const jwt = await createJWT({nbf: PAST}, {issuer:did, signer})
      await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrow()
    })
    it('passes when nbf is in the past and iat is in the future', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.8BPiSG2e6UBn1osnJ6PJYbPjtPMPaCeutTA9OCp-ZzI-QvvwPCVrrWqTu2YELbzUPwDIJCQ8v8N77xCEjIYSmQ'
      // const jwt = await createJWT({nbf:PAST,iat:FUTURE},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrow()
    })
    it('fails when nbf is in the future', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.rcFuhVHtie3Y09pWxBSf1dnjaVh6FFQLHh-83N-uLty3M5ADJ-jVFFkyt_Eupl8Kr735-oPGn_D1Nj9rl4s_Kw'
      // const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
    })
    it('passes when nbf is in the future and policy for nbf is false', async () => {
      expect.assertions(2)
      // const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
      const jwt = await createJWT(
        { requested: ['name', 'phone'], nbf: new Date().getTime() + 1000000 },
        {
          issuer: did,
          signer,
        }
      )
      expect(verifier.verify(jwt)).toBe(true)
      const { payload } = await verifyJWT(jwt, { resolver, policies: { nbf: false } })
      return expect(payload).toBeDefined()
    })
    it('passes when nbf is in the future and now is provided to be higher than nbf', async () => {
      expect.assertions(2)
      // const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
      const jwt = await createJWT(
        { requested: ['name', 'phone'], nbf: new Date().getTime() + 10000 },
        {
          issuer: did,
          signer,
        }
      )
      expect(verifier.verify(jwt)).toBe(true)
      const { payload } = await verifyJWT(jwt, { resolver, policies: { now: new Date().getTime() + 100000 } })
      return expect(payload).toBeDefined()
    })
    it('fails when nbf is in the future and iat is in the past', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.JjEn_huxI9SsBY_3PlD0ShpXvrRgUGFDKAgxJBc1Q5GToVpUTw007-o9BTt7JNi_G2XWmcu2aXXnDn0QFsRIrg'
      // const jwt = await createJWT({nbf:FUTURE,iat:PAST},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
    })
    it('passes when nbf is in the future and iat is in the past with nbf policy false', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.JjEn_huxI9SsBY_3PlD0ShpXvrRgUGFDKAgxJBc1Q5GToVpUTw007-o9BTt7JNi_G2XWmcu2aXXnDn0QFsRIrg'
      // const jwt = await createJWT({nbf:FUTURE,iat:PAST},{issuer:did,signer})
      const { payload } = await verifyJWT(jwt, { resolver, policies: { nbf: false } })
      expect(payload).toBeDefined()
    })
    it('passes when nbf is missing and iat is in the past', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.jkzN5kIVtuRU-Fjte8w5r-ttf9OfhdN38oFJd61CWdI5WnvU1dPCvnx1_kdk2D6Xg-uPqp1VXAb7KA2ZECivmg'
      // const jwt = await createJWT({iat:PAST},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrowError()
    })
    it('fails when nbf is missing and iat is in the future', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.FJuHvf9Tby7b4I54Cm1nh8CvLg4QH2wt2K0WfyQaLqlr3NKKI5hAdLalgZksI25gLhNrZwQFnC-nzEOs9PI1SQ'
      // const jwt = await createJWT({iat:FUTURE},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
    })
    it('passes when nbf is missing and iat is in the future with iat policy to be false', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.FJuHvf9Tby7b4I54Cm1nh8CvLg4QH2wt2K0WfyQaLqlr3NKKI5hAdLalgZksI25gLhNrZwQFnC-nzEOs9PI1SQ'
      // const jwt = await createJWT({iat:FUTURE},{issuer:did,signer})
      const { payload } = await verifyJWT(jwt, { resolver, policies: { iat: false } })
      return expect(payload).toBeDefined()
    })
    it('passes when nbf and iat are both missing', async () => {
      expect.assertions(1)
      const jwt =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.KgnwgMMz-QSOtpba2QMGHMWJoLvhp-H4odjjX1QKnqj4-8dkcK12y7rj7Zq24-1d-1ne86aJCdWtx5VJv3rM7w'
      // const jwt = await createJWT({iat:undefined},{issuer:did,signer})
      await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrowError()
    })
  })
  it('handles ES256K-R algorithm', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ hello: 'world' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toMatchSnapshot()
  })
  it('handles ES256K-R algorithm with publicKeyHex address', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer: recoverySigner, alg: 'ES256K-R' })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toMatchSnapshot()
  })
  it('handles ES256K algorithm with ethereum address - github #14', async () => {
    expect.assertions(1)
    const ethResolver = {
      resolve: jest.fn().mockReturnValue({
        didDocument: {
          id: did,
          publicKey: [
            {
              id: `${did}#keys-1`,
              type: 'Secp256k1VerificationKey2018',
              owner: did,
              ethereumAddress: address,
            },
          ],
        },
      }),
    } as Resolvable
    const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer, alg: 'ES256K' })
    const { payload } = await verifyJWT(jwt, { resolver: ethResolver })
    return expect(payload).toMatchSnapshot()
  })
  it('handles ES256K algorithm with blockchainAccountId - github #14, #155', async () => {
    expect.assertions(1)
    const ethResolver = {
      resolve: jest.fn().mockReturnValue({
        didDocument: {
          id: did,
          publicKey: [
            {
              id: `${did}#keys-1`,
              type: 'EcdsaSecp256k1RecoveryMethod2020',
              owner: did,
              blockchainAccountId: `${address}@eip155:1`,
            },
          ],
        },
      }),
    } as Resolvable
    const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer, alg: 'ES256K' })
    const { payload } = await verifyJWT(jwt, { resolver: ethResolver })
    return expect(payload).toMatchSnapshot()
  })
  it('handles ES256K-R algorithm with checksum address in blockchainAccountId - github #231', async () => {
    expect.assertions(1)
    const verificationMethod = {
      id: `${did}#keys-1`,
      type: 'EcdsaSecp256k1RecoveryMethod2020',
      owner: did,
      blockchainAccountId: `eip155:1:${getAddress(address)}`,
    }
    const ethResolver = {
      resolve: jest.fn().mockReturnValue({
        didDocument: {
          id: did,
          verificationMethod: [verificationMethod],
        },
      }),
    } as Resolvable
    const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer: recoverySigner, alg: 'ES256K-R' })
    const result = await verifyJWT(jwt, { resolver: ethResolver })
    return expect(result.signer).toEqual(verificationMethod)
  })
  it('accepts a valid exp', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ exp: NOW }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toBeDefined()
  })
  it('rejects an expired JWT', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ exp: NOW - NBF_SKEW - 1 }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT has expired/)
  })
  it('accepts an expired JWT with exp policy false', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ exp: NOW - NBF_SKEW - 1 }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, { resolver, policies: { exp: false } })
    return expect(payload).toBeDefined()
  })
  it('rejects an expired JWT without skew time', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ exp: NOW - 1 }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver, skewTime: 0 })).rejects.toThrowError(/JWT has expired/)
  })
  it('accepts an expired JWT without skew time but exp policy false', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ exp: NOW - 1 }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, { resolver, skewTime: 0, policies: { exp: false } })
    return expect(payload).toBeDefined()
  })
  it('accepts a valid audience', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, { resolver, audience: aud })
    return expect(payload).toMatchSnapshot()
  })
  it('accepts multiple audiences', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud: [did, aud] }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, { resolver, audience: aud })
    return expect(payload).toMatchSnapshot()
  })
  it('rejects invalid multiple audiences', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud: [did, did] }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver, audience: aud })).rejects.toThrowError(
      /JWT audience does not match your DID/
    )
  })
  it('accepts a valid audience using callback_url', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
    const { payload } = await verifyJWT(jwt, {
      resolver,
      callbackUrl: 'http://pututu.uport.me/unique',
    })
    return expect(payload).toMatchSnapshot()
  })
  it('rejects invalid audience', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver, audience: did })).rejects.toThrowError(
      /JWT audience does not match your DID or callback url/
    )
  })
  it('accepts invalid audience when override policy is used', async () => {
    expect.assertions(2)
    const jwt = await createJWT({ aud }, { issuer: did, signer })
    const { payload, issuer } = await verifyJWT(jwt, {
      resolver,
      policies: { aud: false },
    })
    expect(payload).toBeDefined()
    expect(issuer).toEqual(did)
  })
  it('rejects an invalid audience using callback_url where callback is wrong', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
    await expect(
      verifyJWT(jwt, {
        resolver,
        callbackUrl: 'http://pututu.uport.me/unique/1',
      })
    ).rejects.toThrowError(/JWT audience does not match your DID or callback url/)
  })
  it('rejects an invalid audience using callback_url where callback is missing', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(
      'JWT audience is required but your app address has not been configured'
    )
  })
  it('rejects invalid audience as no address is present', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ aud }, { issuer: did, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(
      /JWT audience is required but your app address has not been configured/
    )
  })
  it('rejects a pregenerated JWT without iss or client_id', async () => {
    expect.assertions(1)
    const jwt =
      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzN9.aa3_8ZH99MjFoHTrNjOm7Pgq5VL5A13DHR5MTd_dBw2B_pWgNuz4N1tbrocTP0MgDlRbovKmTTDrGNjNMPqH3g'
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT iss or client_id are required/)
  })
  it('rejects a self-issued v2 JWT without sub', async () => {
    expect.assertions(1)
    const jwt = await createJWT({}, { issuer: SELF_ISSUED_V2, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT sub is required/)
  })
  it('rejects a self-issued v2 JWT (sub type: did) with an invalid payload.sub DID', async () => {
    expect.assertions(2)
    const jwt = await createJWT({ sub: 'sub' }, { issuer: SELF_ISSUED_V2, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/DID document not found/)
    expect(resolver.resolve).toHaveBeenCalledWith('sub', { accept: 'application/did+json' })
  })
  it('accepts a self-issued v2 JWT (sub type: did) with a valid payload.sub DID', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ sub: did }, { issuer: SELF_ISSUED_V2, signer })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toBeDefined()
  })
  it('rejects a self-issued v2 JWT (sub type: jkt) without a header.kid DID', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/No DID has been found in the JWT/)
  })
  it('rejects a self-issued v2 JWT (sub type: jkt) with an invalid header.kid DID', async () => {
    expect.assertions(2)
    const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer }, { kid: 'kid' })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/DID document not found/)
    expect(resolver.resolve).toHaveBeenCalledWith('kid', { accept: 'application/did+json' })
  })
  it('accepts a self-issued v2 JWT (sub type: jkt) with a valid header.kid DID', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer }, { kid: did })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toBeDefined()
  })
  it('rejects a self-issued v0.1 JWT without did property', async () => {
    expect.assertions(1)
    const jwt = await createJWT({}, { issuer: SELF_ISSUED_V0_1, signer })
    await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT did is required/)
  })
  it('accepts a self-issued v0.1 JWT with did property', async () => {
    expect.assertions(1)
    const jwt = await createJWT({ did }, { issuer: SELF_ISSUED_V0_1, signer })
    const { payload } = await verifyJWT(jwt, { resolver })
    return expect(payload).toBeDefined()
  })
})
describe('JWS', () => {
  it('createJWS works with JSON payload', async () => {
    expect.assertions(2)
    const payload = { some: 'data' }
    const jws = await createJWS(payload, signer)
    expect(jws).toMatchSnapshot()
    expect(JSON.parse(decodeBase64url(jws.split('.')[1]))).toEqual(payload)
  })
  it('createJWS can canonicalize a JSON payload', async () => {
    expect.assertions(3)
    const payload = { z: 'z', a: 'a' }
    const jws = await createJWS(payload, signer, {}, { canonicalize: true })
    expect(jws).toMatchSnapshot()
    const parsedPayload = JSON.parse(decodeBase64url(jws.split('.')[1]))
    expect(parsedPayload).toEqual(payload)
    expect(JSON.stringify(parsedPayload)).toEqual(JSON.stringify({ a: 'a', z: 'z' }))
  })
  it('createJWS works with base64url payload', async () => {
    expect.assertions(2)
    // use the hex public key as an arbitrary payload
    const encodedPayload = bytesToBase64url(hexToBytes(publicKey))
    const jws = await createJWS(encodedPayload, signer)
    expect(jws).toMatchSnapshot()
    expect(jws.split('.')[1]).toEqual(encodedPayload)
  })
  it('verifyJWS works with JSON payload', async () => {
    expect.assertions(1)
    const payload = { some: 'data' }
    const jws = await createJWS(payload, signer)
    expect(() => verifyJWS(jws, { publicKeyHex: publicKey } as VerificationMethod)).not.toThrow()
  })
  it('verifyJWS works with base64url payload', async () => {
    expect.assertions(1)
    const encodedPayload = bytesToBase64url(hexToBytes(publicKey))
    const jws = await createJWS(encodedPayload, signer)
    expect(() => verifyJWS(jws, { publicKeyHex: publicKey } as VerificationMethod)).not.toThrow()
  })
  it('verifyJWS fails with bad input', async () => {
    expect.assertions(1)
    const badJws = 'abrewguer.fjreoiwfoiew.foirheogu.reoguhwehrg'
    expect(() => verifyJWS(badJws, { publicKeyHex: publicKey } as VerificationMethod)).toThrow('Incorrect format JWS')
  })
})
describe('resolveAuthenticator()', () => {
  const ecKey1 = {
    id: `${did}#keys-1`,
    type: 'Secp256k1VerificationKey2018',
    owner: did,
    publicKeyHex:
      '04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab061',
  }
  const ecKey2 = {
    id: `${did}#keys-2`,
    type: 'Secp256k1SignatureVerificationKey2018',
    owner: did,
    publicKeyHex:
      '04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062',
  }
  const ecKey3 = {
    id: `${did}#keys-3`,
    type: 'Secp256k1SignatureVerificationKey2018',
    owner: did,
    publicKeyHex:
      '04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab063',
  }
  const encKey1 = {
    id: `${did}#keys-4`,
    type: 'Curve25519EncryptionPublicKey',
    owner: did,
    publicKeyBase64: 'QCFPBLm5pwmuTOu+haxv0+Vpmr6Rrz/DEEvbcjktQnQ=',
  }
  const edKey = {
    id: `${did}#keys-5`,
    type: 'ED25519SignatureVerification',
    owner: did,
    publicKeyBase64: 'BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=',
  }
  const edKey2 = {
    id: `${did}#keys-6`,
    type: 'ED25519SignatureVerification',
    owner: did,
    publicKeyBase64: 'SI+tzELqRb8XKuRE3Cj7uWGgkEQ86X87ZjhGAok+Ujc=',
  }
  const authKey1 = {
    type: 'Secp256k1SignatureAuthentication2018',
    publicKey: ecKey1.id,
  }
  const authKey2 = {
    type: 'Secp256k1SignatureAuthentication2018',
    publicKey: ecKey2.id,
  }
  const edAuthKey = {
    type: 'ED25519SigningAuthentication',
    publicKey: edKey.id,
  }
  const edKey6 = {
    id: `${did}#keys-auth6`,
    type: 'ED25519SignatureVerification',
    owner: did,
    publicKeyBase58: 'dummyvalue',
  }
  const ecKey7 = {
    id: `${did}#keys-auth7`,
    type: 'EcdsaSecp256k1VerificationKey2019',
    owner: did,
    publicKeyBase58: 'dummyvalue',
  }
  const edKey8 = {
    id: `${did}#keys-auth8`,
    type: 'Ed25519VerificationKey2018',
    owner: did,
    publicKeyBase58: 'dummyvalue',
  }
  const singleKey = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
      publicKey: [ecKey1],
    },
  }
  const multipleKeysLegacy = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
      publicKey: [ecKey1, ecKey2, ecKey3, encKey1, edKey, edKey2],
      authentication: [authKey1, authKey2, edAuthKey],
    },
  }
  const multipleAuthTypes = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
      publicKey: [ecKey1, ecKey2, ecKey3, encKey1, edKey, edKey2, edKey6, ecKey7],
      authentication: [authKey1, authKey2, edAuthKey, `${did}#keys-auth6`, `${did}#keys-auth7`, edKey8],
    },
  }
  const unsupportedFormat = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
      publicKey: [encKey1],
    },
  }
  const noPublicKey = {
    didDocument: {
      '@context': 'https://w3id.org/did/v1',
      id: did,
    },
  }
  describe('DID', () => {
    describe('ES256K', () => {
      it('finds public key', async () => {
        expect.assertions(1)
        const authenticators = await resolveAuthenticator(
          { resolve: jest.fn().mockReturnValue(singleKey) } as Resolvable,
          alg,
          did
        )
        return expect(authenticators).toEqual({
          authenticators: [ecKey1],
          issuer: did,
          didResolutionResult: singleKey,
        })
      })
      it('filters out irrelevant public keys', async () => {
        expect.assertions(1)
        const authenticators = await resolveAuthenticator(
          { resolve: jest.fn().mockReturnValue(multipleKeysLegacy) } as Resolvable,
          alg,
          did
        )
        return expect(authenticators).toEqual({
          authenticators: [ecKey1, ecKey2, ecKey3],
          issuer: did,
          didResolutionResult: multipleKeysLegacy,
        })
      })
      it('only list authenticators able to authenticate a user', async () => {
        expect.assertions(1)
        const authenticators = await resolveAuthenticator(
          { resolve: jest.fn().mockReturnValue(multipleKeysLegacy) } as Resolvable,
          alg,
          did,
          'authentication'
        )
        return expect(authenticators).toEqual({
          authenticators: [ecKey1, ecKey2],
          issuer: did,
          didResolutionResult: multipleKeysLegacy,