digitalbazaar / vc

W3C Verifiable Credentials implementation in JavaScript
BSD 3-Clause "New" or "Revised" License
177 stars 52 forks source link

getAssertionMethod() function returns error #66

Closed skradha26 closed 4 years ago

skradha26 commented 4 years ago

(node:22207) UnhandledPromiseRejectionWarning: TypeError: Cannot read property '0' of undefined.

I suspect some issue with the did.json file I published in the URL given in the code. Can someone help me here?

const vc = require('vc-js');
const jsigs = require('jsonld-signatures');
//const didContext = require('./lib/contexts');

const {keyToDidDoc} = require('did-method-key').driver();
const {Ed25519KeyPair} = require('crypto-ld');
const {Ed25519Signature2018} = jsigs.suites;
const {defaultDocumentLoader} = vc;
const driver = require('did-method-key').driver();
const request = require('request');

// Sample unsigned credential
const credential = {
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://www.w3.org/2018/credentials/examples/v1"
  ],
  "id": "https://example.com/credentials/1872",
  "type": ["VerifiableCredential", "AlumniCredential"],
  "issuanceDate": "2010-01-01T19:23:24Z",
  "credentialSubject": {
    "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
    "alumniOf": "UND"
  }

};

let issuerKeyPair, issuerKeyDid = null;
let subjectKeyPair, subjectKeyDid = null;

const getAssertionMethod = didKey => {
  return didKey.assertionMethod[0];
};

const documentLoader = async url => {
  // this fetches the entire did document
  // this fetches a specific a key from a did doc by fingerPrint

  // IB: This is how DIDs are resolved to their DIDdocs...

  // Don't think this top block is used by me so far - I think it resolves different DID types
  if(issuerKeyDid['@context'] === url) {
    return {
      contextUrl: null,
      documentUrl: url,
      // this did key's context should resolve
      // to the latest did-context
      document: didContext.contexts.get('https://w3id.org/did/v0.11')
    };
  }

  // The next two clauses resolved a did:key document, which is self resolvable to its DIDdoc/JSON-LD
  if(url.startsWith('did:key') && url.includes('#')) {
    const did = url.split('#')[0];

    const didKey = await driver.get({did});
    let doc = null;
    for(const prop in didKey) {
      const property = didKey[prop];
      if(Array.isArray(property)) {
        [doc] = property.filter(p => p.id === url);
      }
      if(property.id === url) {
        doc = didKey[prop];
      }
      if(doc) {
        break;
      }
    }
    doc['@context'] = 'https://w3id.org/security/v2';
    return {
      contextUrl: null,
      documentUrl: url,
      document: doc
    };
  }

  if(url.startsWith('did:key')) {
    const did = url.split('#')[0];
    const doc = await driver.get({did});
    doc['@context'] = 'https://w3id.org/security/v2';

    return {
      contextUrl: null,
      documentUrl: url,
      document: doc
    }
  }

  // this adds schema.org's JSON-LD context to the documentLoader
  // the demo credential's type is from schema.org
  // IB - I haven't used this yet... 
  if(url === 'https://schema.org/') {
    return {
      contextUrl: null,
      documentUrl: url,
      document: schemaContext
    };
  }

   if(url.startsWith('did:web:craftofscience.xyz'))
  {
    url = "www.craftofscience.xyz/did.json"

    let docString = await downloadPage(url)

    let doc = JSON.parse(docString);

    doc['@context'] = 'https://w3id.org/security/v1';

    console.log("DID Doc fetched from did:web URL:\n", doc, "\n")

    return {
      contextUrl: null,
      documentUrl: url,
      document: doc
    };
  }

  return defaultDocumentLoader(url);
};

async function setupIssuerKeyPair() {

    const privateKeyBase58 = 'removed from here'
  // this produces a keypair with private key material
  // used for signing credentials / presentations
   const options = {
    publicKeyBase58: 'B1BoAENvkZFa1EMnXksUUg1JFeZGAg2CWrLYMzdquA4i',
    privateKeyBase58
     };
   issuerKeyPair = new Ed25519KeyPair(options);
  issuerKeyPair.id = 'did:web:craftofscience.xyz#' + issuerKeyPair.fingerprint();
  issuerKeyDid = {"id":"did:web:craftofscience.xyz"}
}

async function setupSubjectKeyPair() {
  // this produces a keypair with private key material
  // in a real situation, subject might provide their DID 
  subjectKeyPair = await Ed25519KeyPair.generate();

  // this is a did key document which only contains
  // the public key material used to verify a credential
  subjectKeyDid = keyToDidDoc(subjectKeyPair);
  subjectKeyPair.id = getAssertionMethod(subjectKeyDid);

  console.log('subjectKeyDid', subjectKeyDid, '\n');
  console.log('subjectKeyPair', subjectKeyPair, '\n');
  console.log(subjectKeyPair.id);

}

async function  signCredential () {

  // Issue a credential to the subject...
  const signingSuite = new Ed25519Signature2018({
    verificationMethod: getAssertionMethod(issuerKeyDid),
    key: issuerKeyPair
  });

  credential.issuer = issuerKeyDid.id;
  credential.credentialSubject.id = subjectKeyDid.id
  const issuedCred = await vc.issue({credential, suite: signingSuite,documentLoader});
  console.log("Issued Credential:\n", issuedCred, "\n")

  const verifySuite = new Ed25519Signature2018();
  //console.log("issuecred-----\n", issuedCred);
  //console.log("verifySuite-----\n", verifySuite);
  const credVerified = await vc.verifyCredential({credential: issuedCred,suite: verifySuite, documentLoader});
 // console.log(credVerified);
  console.log('CREDENTIAL VERIFIED:\n', JSON.stringify(credVerified));

}

(async ()=>{
  await setupIssuerKeyPair();
  await setupSubjectKeyPair();
 await signCredential();
})();

//const signedVC = await vc.issue({credential, suite});
//console.log(JSON.stringify(signedVC, null, 2));
mattcollier commented 4 years ago

Hello, is there a stack trace that goes along with your error at the very top?

dlongley commented 4 years ago

Also, make sure to use the DID context: https://w3id.org/did/v0.11 -- the one you're using is unstable and out-of-date.

mattcollier commented 4 years ago

The context referenced by @dlongley is available here: https://github.com/digitalbazaar/did-context

skradha26 commented 4 years ago

Thank you. I think it worked. But, when I try to issue credential, I get the below error,

(node:1984) UnhandledPromiseRejectionWarning: jsonld.InvalidUrl: Dereferencing a URL did not result in a valid JSON-LD object. Possible causes are an inaccessible URL perhaps due to a same-origin policy (ensure the server uses CORS if you are using client-side JavaScript), too many redirects, a non-JSON response, or more than one HTTP Link Header was provided for a remote context.
    at ContextResolver._fetchContext (/Users/sradha/PhD_Research_2020/digitalbazaar_experiments/vc-js/node_modules/jsonld/lib/ContextResolver.js:159:13)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:229:7)
    at Function.Module.runMain (module.js:696:11)
    at startup (bootstrap_node.js:204:16)
    at bootstrap_node.js:625:3
(node:1984) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 7)
(node:1984) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

This is my issueCredential code:

async function  issueCredential () {

 console.log("Credential issuing service...\n");

 const credentialString = fs.readFileSync("./credential.json").toString();
 console.log(credentialString);
 const credential = JSON.parse(credentialString);

 credential.issuer.id = issuerKeyDid.id;
 credential.credentialSubject.id = subjectKeyDid.id
 credential.issuanceDate = (new Date()).toISOString()

 const signingSuite = new Ed25519Signature2018({
  purpose: new AssertionProofPurpose(),
  key: issuerKeyPair
});

  let issuedCred = await vc.issue({credential, suite: signingSuite, documentLoader: documentLoader, });
  console.log("Issued Credential:\n", JSON.stringify(issuedCred), "\n")
  fs.writeFile("./issuedCredential.json", JSON.stringify(issuedCred, null, 4), (err) => {
  if (err) {
        console.error(err);
        return;
  }});
};

And below is credential.json
{
    "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "www.craftofscience.xyz/custContext.jsonld"
    ],
    "id": "www.craftofscience.xyz/custContext.jsonld",
    "type": [
        "VerifiableCredential",
        "TaleData"
    ],
    "issuer": {
        "id": "did:web:craftofscience.xyz"
    },
    "issuanceDate": "2020-03-06T16:24:58.160Z",
    "credentialSubject": {
        "id": "did:web:craftofscience.xyz",
        "type": "TaleData",
        "version": "1"
    }
}
mattcollier commented 4 years ago

Hello, the error you're getting now is because you'll need a document loader. Please see:

https://github.com/digitalbazaar/vc-js#custom-documentLoader

skradha26 commented 4 years ago

Thank you. That helped. Apologies for posting multiple questions in the same issue. When I try to issue the credential, I get this error: (node:2223) UnhandledPromiseRejectionWarning: TypeError: "suite.verificationMethod" property is required.

But I do see the verification method in the signing suite:

signing suite Ed25519Signature2018 {
  type: 'Ed25519Signature2018',
  creator: undefined,
  verificationMethod: 'did:web:craftofscience.xyz#z6MkpTSqkUdN66k37jCVDKqKKmZJ5Dq7aZGZCsFUCGbrpNr6',
  proof: undefined,
  useNativeCanonize: undefined,
  alg: 'EdDSA',
  LDKeyClass: [Function: Ed25519KeyPair],
  signer: { sign: [AsyncFunction: sign] },
  key: 
   Ed25519KeyPair {
     passphrase: null,
     id: 'did:web:craftofscience.xyz#z6MkpTSqkUdN66k37jCVDKqKKmZJ5Dq7aZGZCsFUCGbrpNr6',
     controller: undefined,
     owner: undefined,
     type: 'Ed25519VerificationKey2018',
     privateKeyBase58: '',
     publicKeyBase58: 'B1BoAENvkZFa1EMnXksUUg1JFeZGAg2CWrLYMzdquA4i' },
  verifier: { verify: [AsyncFunction: verify] },
  requiredKeyType: 'Ed25519VerificationKey2018' }