decentralized-identity / did-jwt

Create and verify DID verifiable JWT's in Javascript
Apache License 2.0
331 stars 70 forks source link

[BUG] no_suitable_keys: DID document for `did:polygon:testnet:0x...` does not have public keys for ES256K-R #275

Closed francesco-plt closed 1 year ago

francesco-plt commented 1 year ago

Current Behavior

I cannot use did-jwt-vc library to verify VCs because the traceback reports the following error happening in did-jwt:

Uncaught Error Error: no_suitable_keys: DID document for did:polygon:testnet:0x... does not have public keys for ES256K-R
    at <anonymous> (node_modules/did-jwt-vc/node_modules/did-jwt/lib/index.module.js:928:15)
    at processTicksAndRejections (internal/process/task_queues:95:5)

Expected Behavior

The verification process should work as usual even if I am using a custom Issuer in the createVerifiableCredentialJwt function and a custom getResolver function to initialize the Resolver object used in the following function call const verificationStatus = await verifyCredential(vcJwt, resolver);. This is weird because the error Error: no_suitable_keys: DID document for did:polygon:testnet:0x... does not have public keys for ES256K-R does not make sense: if I do not use custom Issuer and getResolver the process of verification ideally works fine without errors. The problem is that my implementation in code needs those objects to be customizable.

Failure Information

Steps to Reproduce

Step 1: create a VC using a custom issuer

Both the issuer and the sub of the VC are DIDs with method polygon:testnet (e.g. did:polygon:testnet:0x...).

const vcJwt = await createVerifiableCredentialJwt(
    payload,
    {
      did: issuer.getDid(),
      alg: 'ES256K-R',
      signer: ES256KSigner(hexToBytes(issuerConf.privateKey.replace('0x', '')), true),
    }
  )
Step 2: verify the VC using a custom resolver
const resolver = new Resolver(PolyGetResolver());
const verificationStatus = await verifyCredential(vcJwt, resolver);

Note: the custom resolver works correctly, if I run:

const resolver = new Resolver(PolyGetResolver());
await resolver.resolve(issuer.getDid());

The DID document is returned correctly.

mirceanis commented 1 year ago

It would be very helpful if you were able to link to a project that reproduces this error. Where can I find the implementation of PolyGetResolver() ? Can you paste some example DIDs/documents at least?

francesco-plt commented 1 year ago

It would be very helpful if you were able to link to a project that reproduces this error. Where can I find the implementation of PolyGetResolver() ? Can you paste some example DIDs/documents at least?

The code above is enough to reproduce the error. If you have a Polygon Wallet and you register a DID using the mumbai testnet, when resolving you end up with a DID document that looks like the following:

{
  "@context": "https://w3id.org/did/v1",
  "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
  "verificationMethod": [
    {
      "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
      "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
    }
  ],
  "authentication": [
    "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
    {
      "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
      "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
    }
  ],
  "assertionMethod": [
    "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
    {
      "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
      "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
    }
  ]
}

And this is the code for PolyGetResolver:

import { getResolver } from '@ayanworks/polygon-did-resolver';
export function PolyGetResolver() {
  async function resolve(
    did: string,
    parsed: ParsedDID,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    _unused: Resolvable,
    options: DIDResolutionOptions
  ): Promise<DIDResolutionResult> {
    // console.log(parsed)
    // {method: 'mymethod', id: 'abcdefg', did: 'did:mymethod:abcdefg/some/path#fragment=123', path: '/some/path', fragment: 'fragment=123'}
      const polyResolver = getResolver();
    const resolver = new Resolver(polyResolver as ResolverRegistry);
    try {
        const didResolutionResult = await resolver.resolve(did) as any;
        if (!didResolutionResult) {
            throw new Error('Failed to resolve DID');
        }

        return {
            didDocument: JSON.parse(didResolutionResult.didDocument),
            didDocumentMetadata: didResolutionResult.didDocumentMetadata,
            didResolutionMetadata: didResolutionResult.didResolutionMetadata
        }
    } catch (error) {
        throw error;
    }
  }

  return { polygon: resolve }
}

The function is built following the example for custom resolvers given by did-resolver README. If a resolver object is passed the PolyGetResolver function, by calling resolve.resolve(...) the DID document is retrieved correctly. The problem is that I am not able to verify correctly VCs because of the error I reported in the title of the issue. I have not been able to pin down the issue to a specific step of the issuing/verifying process because I am not sure that I am even signing the credentials right:

const issuer = {
            did: "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
            alg: 'ES256K',
            signer: ES256KSigner(hexToBytes(privateKey))
        };
        const vcJwt = await createVerifiableCredentialJwt(claim, issuer);

The code above looks fine in theory, but the VCs it produces are marked as invalid signature by jwt.io, for example the following one:

eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDpwb2x5Z29uOnRlc3RuZXQ6MHg5MjBCNTdFNjdjZWViNWRkMTc1MTNENWY1ODZlNDgyNzI3ZTI1NGQxIiwibmJmIjoxNTYyOTUwMjgyLCJpc3MiOiJkaWQ6cG9seWdvbjp0ZXN0bmV0OjB4RkFmQTZEZjg2NzBlZDAyRDY2RjFEQ2YzRDY1QTY2RTg3OUNmRTg0MyJ9.yVx01U0DlkqomOMDbHRQ31bjG8rzEPkMtZu5h7leDSdZ-kbd1qauzjzfiQKPxSEMlDGWJNAGepK71Tdt3mTNAA
mirceanis commented 1 year ago

When I try the resolver code you gave me I get an error in the polygon-did-resolver code: [ERROR] default - Error occurred in resolve function Error: The DID document for the given DID was not found!

Here is my test project: https://github.com/mirceanis/ts-ethr-did/blob/main/src/didjwt-275.ts

Please post a project that manifests the error you reported (you can also use my project and make a PR there).

Mencucci commented 1 year ago

Here is my test project https://github.com/Mencucci/did-jwt-275 . In this project the resolution works, but the verification of the JWT generated in the messages above still fails. The error is different invalid_signature. The project linked here shows the following output, including traceback, on my machine

C:\Program Files\nodejs\node.exe .\dist\app.js
resolutionResult
{
  "didDocument": {
    "@context": "https://w3id.org/did/v1",
    "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
    "verificationMethod": [
      {
        "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
        "type": "EcdsaSecp256k1VerificationKey2019",
        "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
        "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
      }
    ],
    "authentication": [
      "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
      {
        "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
        "type": "EcdsaSecp256k1VerificationKey2019",
        "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
        "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
      }
    ],
    "assertionMethod": [
      "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
      {
        "id": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1",
        "type": "EcdsaSecp256k1VerificationKey2019",
        "controller": "did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",
        "publicKeyBase58": "7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"
      }
    ]
  },
  "didDocumentMetadata": {},
  "didResolutionMetadata": {
    "contentType": "application/did+ld+json"
  }
}
Uncaught Error Error: invalid_signature: Signature invalid for JWT
    at verifyES256K (d:\OneDrive - Politecnico di Milano\Polimi\A.A. 2022-2023\tesi\blockchain\ts-ethr-did-v4\node_modules\did-jwt-vc\node_modules\did-jwt\lib\index.module.js:718:22)
    at verifyJWSDecoded (d:\OneDrive - Politecnico di Milano\Polimi\A.A. 2022-2023\tesi\blockchain\ts-ethr-did-v4\node_modules\did-jwt-vc\node_modules\did-jwt\lib\index.module.js:1284:47)
    at <anonymous> (d:\OneDrive - Politecnico di Milano\Polimi\A.A. 2022-2023\tesi\blockchain\ts-ethr-did-v4\node_modules\did-jwt-vc\node_modules\did-jwt\lib\index.module.js:1031:30)
    at processTicksAndRejections (internal/process/task_queues:95:5)
Process exited with code 1

Important note: polygon-did-resolver has a dependency on "ethers": "5.4.7", exact version. This is not specified anywhere, but earlier and later versions cause the resolution to fail. npm i is recommended before running the project or in case of DID resolution errors.

I am Francesco's partner in this project, we've worked on this together. This happens to be the first working minimal example of this error. Thank you for your help

francesco-plt commented 1 year ago

It looks like that the verification process fails because of a problem in the verifyES256K function inside did-jwt. The function tries to retrieve the public key of the signer from its DID document, part of which is contained in a variable called fullPublicKeys. The following code snippet returns false and the verification process fails:

let signer = fullPublicKeys.find(pk => {
    try {
      const pubBytes = extractPublicKeyBytes(pk);
      const a = secp256k1.keyFromPublic(pubBytes);
      return a.verify(hash, sigObj);
    } catch (err) {
      return false;
    }
  });

If I hardcode the public key of the signer into the library as follows:

  signer = "7Lnm1ezAQuGXLZH9ruHbWGo6P6t4F1yqm8UaNATW53MFDoGgxABiupKMNtPz8WDCr4w3bdW7ukkY9cpdh6VvTTMDibgs4";

The verification is succesful:

  {"verified":true,"payload":{"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential"],"credentialSubject":{"degree":{"type":"BachelorDegree","name":"Baccalauréat en musiques numériques"}}},"sub":"did:polygon:testnet:0x920B57E67ceeb5dd17513D5f586e482727e254d1","nbf":1562950282,"iss":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843"},"didResolutionResult":{"didDocument":{"@context":"https://w3id.org/did/v1","id":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843","verificationMethod":[{"id":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1","type":"EcdsaSecp256k1VerificationKey2019","controller":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843","publicKeyBase58":"7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"}],"authentication":["did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",{"id":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1","type":"EcdsaSecp256k1VerificationKey2019","controller":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843","publicKeyBase58":"7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"}],"assertionMethod":["did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843",{"id":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843#key-1","type":"EcdsaSecp256k1VerificationKey2019","controller":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843","publicKeyBase58":"7Lnm1exz9mqBpRfjiafdQS1N9PmkB9vhjApyWZmYaudUzstCKjzBsG9sAjxPdSmFYFZ9i7d3PsfzdSMWsquvhpBNeFTNf"}]},"didDocumentMetadata":{},"didResolutionMetadata":{"contentType":"application/did+ld+json"}},"issuer":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843","signer":"7Lnm1ezAQuGXLZH9ruHbWGo6P6t4F1yqm8UaNATW53MFDoGgxABiupKMNtPz8WDCr4w3bdW7ukkY9cpdh6VvTTMDibgs4","jwt":"eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDpwb2x5Z29uOnRlc3RuZXQ6MHg5MjBCNTdFNjdjZWViNWRkMTc1MTNENWY1ODZlNDgyNzI3ZTI1NGQxIiwibmJmIjoxNTYyOTUwMjgyLCJpc3MiOiJkaWQ6cG9seWdvbjp0ZXN0bmV0OjB4RkFmQTZEZjg2NzBlZDAyRDY2RjFEQ2YzRDY1QTY2RTg3OUNmRTg0MyJ9.yVx01U0DlkqomOMDbHRQ31bjG8rzEPkMtZu5h7leDSdZ-kbd1qauzjzfiQKPxSEMlDGWJNAGepK71Tdt3mTNAA","policies":{},"verifiableCredential":{"credentialSubject":{"degree":{"type":"BachelorDegree","name":"Baccalauréat en musiques numériques"},"id":"did:polygon:testnet:0x920B57E67ceeb5dd17513D5f586e482727e254d1"},"issuer":{"id":"did:polygon:testnet:0xFAfA6Df8670ed02D66F1DCf3D65A66E879CfE843"},"type":["VerifiableCredential"],"@context":["https://www.w3.org/2018/credentials/v1"],"issuanceDate":"2019-07-12T16:51:22.000Z","proof":{"type":"JwtProof2020","jwt":"eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDpwb2x5Z29uOnRlc3RuZXQ6MHg5MjBCNTdFNjdjZWViNWRkMTc1MTNENWY1ODZlNDgyNzI3ZTI1NGQxIiwibmJmIjoxNTYyOTUwMjgyLCJpc3MiOiJkaWQ6cG9seWdvbjp0ZXN0bmV0OjB4RkFmQTZEZjg2NzBlZDAyRDY2RjFEQ2YzRDY1QTY2RTg3OUNmRTg0MyJ9.yVx01U0DlkqomOMDbHRQ31bjG8rzEPkMtZu5h7leDSdZ-kbd1qauzjzfiQKPxSEMlDGWJNAGepK71Tdt3mTNAA"}}}

The problem arises from the following portion of the source code of did-jwt: https://github.com/decentralized-identity/did-jwt/blob/d898aa613a856cc7d3b2f2cc70ce4d8452b4e667/src/VerifierAlgorithm.ts#L106-L112

Screenshot 2023-03-27 alle 17 57 01
mirceanis commented 1 year ago

If I hardcode the public key of the signer into the library as follows:

signer = "7Lnm1ezAQuGXLZH9ruHbWGo6P6t4F1yqm8UaNATW53MFDoGgxABiupKMNtPz8WDCr4w3bdW7ukkY9cpdh6VvTTMDibgs4";

Can you clarify what you mean by hardcoding the signer?

mirceanis commented 1 year ago

I was able to debug what's going on using @Mencucci 's project.

It seems that the publicKeyBase58 you are returning in the resolver is incorrect. For a Secp256k1 key it should represent an encoding of the raw public key bytes which is 33 bytes long in compressed form, or 65 bytes in uncompressed form. The publicKeyBase58 value you are returning decodes to 68 bytes.

After further investigation, I realized that the 68 bytes represent the base58btc encoding of the utf-8 encoding of the hex encoded compressed public key prefixed with 0x.

I know.. right?

It seems that somewhere in the pipeline a publicKeyHex is getting encoded to base58 instead of the actual bytes of the public key. And, if I interpret the key according to this incorrect encoding, then verification works.

Please check your code again for the step where you compute the publicKeyBase58 that goes into the DID document.

I will close this issue as it doesn't represent a bug in did-jwt.