// did:ssb test vector generator for DID Test Suite const DIDSSBResolver = require('./resolver'); const dest = process.argv[2]; if (!dest) throw 'Usage: node gen-test.js '; const path = require('path'); const fs = require('fs'); const didJsonFilename = path.join(dest, 'did-ssb.json'); const resolverJsonFilename = path.join(dest, 'resolver-ssb.json'); const dereferencerJsonFilename = path.join(dest, 'dereferencer-ssb.json'); const testCommon = { didMethod: 'did:ssb', implementation: 'ssb-did-resolver', implementer: 'Secure Scuttlebutt Consortium', }; const didMethodInputs = [ { did: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU', resolutionOptions: {}, representationResolutionOptions: {} } ]; const resolverInputs = [ { function: 'resolve', did: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU', resolutionOptions: {} }, { function: 'resolveRepresentation', did: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU', resolutionOptions: {} }, { function: 'resolve', did: 'did:ssb:ed25519:6RpN4Ztw3jLwzQtHl8XpnnR58LWZTAjwq2vvfyx7zkc', resolutionOptions: {} } ]; const dereferencerInputs = [ { didUrl: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU', dereferenceOptions: {} }, { didUrl: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU?versionId=%25Vlo6kAc%2BIbGGBhD2MUi2r3ULz%2FNAGBWwGb%2FEMa4w4FI%3D.sha256', dereferenceOptions: {} }, { didUrl: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU?versionTime=2021-07-24T03:38:45Z', dereferenceOptions: {} } ]; let didParameters = { versionId: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU?versionId=%25Vlo6kAc%2BIbGGBhD2MUi2r3ULz%2FNAGBWwGb%2FEMa4w4FI%3D.sha256', versionTime: 'did:ssb:ed25519:f_6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU?versionTime=2021-07-24T03:38:46Z' }; const ssbClient = require('ssb-client'); let closeSbot, resolver; ssbClient(function (err, sbot, config) { if (err) throw err; closeSbot = sbot.close.bind(sbot); resolver = new DIDSSBResolver(sbot, config); generateReports(done) }); function done() { closeSbot(function (err) { if (err && err !== true) throw err; }); } function generateReports(next) { let reportWaiting = 3; generateMethodReport(reportNext); generateResolverReport(reportNext); generateDereferencerReport(reportNext); function reportNext() { if (--reportWaiting) return; next(); } } function generateMethodReport(next) { let contentTypes = {}; let supportedContentTypes = []; let dids = []; let didMethodReport = { ...testCommon, supportedContentTypes, dids, didParameters, }; let methodWaiting = 0; didMethodInputs.forEach(function (input) { methodWaiting++; const did = input.did; const didReport = didMethodReport[did] = {}; dids.push(did); let didWaiting = 2; let resResult, resReprResult; resolver.resolve(did, input.resolutionOptions, function (didResolutionMetadata, didDocument, didDocumentMetadata) { if (didResolutionMetadata.error) throw didResolutionMetadata.error; resResult = {didResolutionMetadata, didDocument, didDocumentMetadata}; didNext(); }); resolver.resolveRepresentation(did, input.representationResolutionOptions, function (didResolutionMetadata, didDocumentStream, didDocumentMetadata) { if (didResolutionMetadata.error) throw didResolutionMetadata.error; resReprResult = {didResolutionMetadata, didDocumentStream, didDocumentMetadata}; didNext(); }); function didNext() { if (--didWaiting) return; const {didDocument} = resResult; let representationSpecificEntries = {}; let properties = {}; for (const property in didDocument) switch (property) { case '@context': representationSpecificEntries[property] = didDocument[property]; break; default: properties[property] = didDocument[property]; break; } didReport.didDocumentDataModel = { properties }; const contentType = resReprResult.didResolutionMetadata.contentType; if (!contentType) throw new Error('Expected contentType'); if (!contentTypes[contentType]) { contentTypes[contentType] = true supportedContentTypes.push(contentType); } didReport[contentType] = { didDocumentDataModel: { representationSpecificEntries }, representation: resReprResult.didDocumentStream, didDocumentMetadata: resReprResult.didDocumentMetadata, didResolutionMetadata: resReprResult.didResolutionMetadata }; methodNext(); } }); function methodNext() { if (--methodWaiting) return; const report = JSON.stringify(didMethodReport, null, 2); fs.writeFileSync(didJsonFilename, report); next(); } } function getOutcome(error, deactivated) { if (error) switch (error) { case 'invalidDid': return 'invalidDidErrorOutcome'; case 'invalidDidUrl': return 'invalidDidUrlErrorOutcome'; case 'notFound': return 'notFoundErrorOutcome'; case 'representationNotSupported': return 'representationNotSupportedErrorOutcome'; default: throw new Error('Unknown error: ' + error); } if (deactivated) return 'deactivatedOutcome'; return 'defaultOutcome'; } function generateResolverReport(next) { let expectedOutcomes = {}; let executions = []; const didResolverReport = { ...testCommon, expectedOutcomes, executions }; let executionWaiting = resolverInputs.length; resolverInputs.forEach(function (input, i) { switch (input.function) { case 'resolve': resolver.resolve(input.did, input.resolutionOptions, function (didResolutionMetadata, didDocument, didDocumentMetadata) { executions[i] = { function: input.function, input: {did: input.did, resolutionOptions: input.resolutionOptions}, output: {didResolutionMetadata, didDocument, didDocumentMetadata} }; const outcome = getOutcome(didResolutionMetadata.error, didDocumentMetadata.deactivated); const idxs = expectedOutcomes[outcome] || (expectedOutcomes[outcome] = []); idxs.push(i); executionNext(); }); break; case 'resolveRepresentation': resolver.resolveRepresentation(input.did, input.resolutionOptions, function (didResolutionMetadata, didDocumentStream, didDocumentMetadata) { executions[i] = { function: input.function, input: {did: input.did, resolutionOptions: input.resolutionOptions}, output: {didResolutionMetadata, didDocumentStream, didDocumentMetadata} }; const outcome = getOutcome(didResolutionMetadata.error, didDocumentMetadata.deactivated); const idxs = expectedOutcomes[outcome] || (expectedOutcomes[outcome] = []); idxs.push(i); executionNext(); }); break; default: throw new Error('Unknown function: ' + input.function); } }); function executionNext() { if (--executionWaiting) return; for (const outcome in expectedOutcomes) { expectedOutcomes[outcome].sort(); } const report = JSON.stringify(didResolverReport, null, 2); fs.writeFileSync(resolverJsonFilename, report); next(); } } function generateDereferencerReport(next) { let expectedOutcomes = {}; let executions = []; const didUrlDereferencerReport = { ...testCommon, expectedOutcomes, executions }; let executionWaiting = dereferencerInputs.length; dereferencerInputs.forEach(function (input, i) { resolver.dereference(input.didUrl, input.dereferenceOptions, function (dereferencingMetadata, contentStream, contentMetadata) { executions[i] = { function: 'dereference', input, output: {dereferencingMetadata, contentStream, contentMetadata} }; const outcome = getOutcome(dereferencingMetadata.error, contentMetadata.deactivated); const idxs = expectedOutcomes[outcome] || (expectedOutcomes[outcome] = []); idxs.push(i); executionNext(); }); }); function executionNext() { if (--executionWaiting) return; for (const outcome in expectedOutcomes) { expectedOutcomes[outcome].sort(); } const report = JSON.stringify(didUrlDereferencerReport, null, 2); fs.writeFileSync(dereferencerJsonFilename, report); next(); } }