Skip to content

Commit

Permalink
Update test suite and fix related bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Jan 1, 2019
1 parent df0eb1d commit 3cbcc73
Show file tree
Hide file tree
Showing 17 changed files with 974 additions and 2,288 deletions.
195 changes: 139 additions & 56 deletions lib/ProofSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,18 @@ module.exports = class ProofSet {
*/
async add(document, {
suite, purpose, documentLoader, expansionMap} = {}) {
if(!(suite && purpose)) {
if(!suite) {
throw new TypeError('"options.suite" must be given.');
}

if(suite.legacy) {
if(purpose) {
throw new TypeError(
`The "${suite.type}" does not support "options.purpose".`);
}
} else if(!purpose) {
throw new TypeError(
'"options.suite" and "options.purpose" must be given.');
`The "${suite.type}" suite requires "options.purpose" be given.`);
}

if(documentLoader) {
Expand All @@ -66,28 +75,50 @@ module.exports = class ProofSet {
document = await documentLoader(document);
}

// TODO: add flag for accepting foreign input context

// shallow clone the document, excluding any existing proof(s)
const input = {...document};
delete input.proof;
if(suite.legacy) {
delete input.signature;
} else {
delete input.proof;
}

// create the new proof (suites MUST output a proof using the security-v2
// `@context`)
const proof = await suite.createProof(
input, {purpose, documentLoader, expansionMap});

// TODO: add flag for compacting to foreign input context

// compact proof to match document's context
const expandedProof = {
'https://w3id.org/security#proof': {
'@graph': proof
}
};
const ctx = jsonld.getValues(document, '@context');
const options = {documentLoader, expansionMap};
const compactProof = await jsonld.compact(expandedProof, ctx, options);
delete compactProof['@context'];

// add proof to document
jsonld.addValue(document, 'proof', compactProof.proof);
if(suite.legacy) {
const expandedProof = {
'https://w3id.org/security#signature': proof
};
const ctx = jsonld.getValues(document, '@context');
const options = {documentLoader, expansionMap};
const compactProof = await jsonld.compact(expandedProof, ctx, options);
delete compactProof['@context'];

// add proof to document
jsonld.addValue(document, 'signature', compactProof.signature);
} else {
const expandedProof = {
'https://w3id.org/security#proof': {
'@graph': proof
}
};
const ctx = jsonld.getValues(document, '@context');
const options = {documentLoader, expansionMap};
const compactProof = await jsonld.compact(expandedProof, ctx, options);
delete compactProof['@context'];

// add proof to document
jsonld.addValue(document, 'proof', compactProof.proof);
}

return document;
}

Expand All @@ -104,10 +135,10 @@ module.exports = class ProofSet {
* `documentLoader`) or a plain object (JSON-LD document).
* @param options {object} Options hashmap.
*
* A `suites` option is required:
* A `suite` option is required:
*
* @param options.suites {Array of LinkedDataSignature} acceptable signature
* suite instances for verifying the proof(s).
* @param options.suite {LinkedDataSignature or Array of LinkedDataSignature}
* acceptable signature suite instances for verifying the proof(s).
*
* A `purpose` option is required:
*
Expand All @@ -132,10 +163,25 @@ module.exports = class ProofSet {
* property will be present.
*/
async verify(document, {
suites, purpose, documentLoader, expansionMap} = {}) {
if(!(suites && purpose)) {
suite, purpose, documentLoader, expansionMap} = {}) {
if(!Array.isArray(suite)) {
suite = [suite];
}
if(suite.length === 0) {
throw new TypeError('At least one suite must be given.');
}

const suites = suite.filter(suite => !suite.legacy);
const legacySuites = suite.filter(suite => suite.legacy);

if(suites.length === 0) {
if(purpose) {
throw new TypeError(
'The given suites do not support "options.purpose".');
}
} else if(!purpose) {
throw new TypeError(
'"options.suites" and "options.purpose" must be given.');
'The given suites require that "options.purpose" be given.');
}

if(documentLoader) {
Expand All @@ -151,54 +197,91 @@ module.exports = class ProofSet {
if(typeof document === 'string') {
// fetch document
document = await documentLoader(document);
} else {
// clone document to allow for removal of `proof` set
document = {...document};
}

const {proof: proofSet} = document;
if(!proofSet) {
throw new Error(
'Could not verify any proofs; the document has no "proof" property.');
}
// TODO: add flag for accepting foreign input context

// compact proofs to security-v2 context
const expanded = {
proof: proofSet
};
const ctx = jsonld.getValues(document, '@context');
expanded['@context'] = ctx;
const options = {documentLoader, expansionMap};
const compact = await jsonld.compact(
expanded, constants.SECURITY_CONTEXT_URL, options);

// filter out matching proofs
const matches = compact.proof.filter(proof => purpose.match(
proof, {document, documentLoader, expansionMap}));
if(matches.length === 0) {
const results = [].concat(...(await Promise.all([
_verify({
document, suites, proofProperty: 'proof',
purpose, documentLoader, expansionMap}),
// FIXME: instead of passing `null` for proof purpose, pass
// `LegacyProofPurpose` instance
_verify({
document, suites: legacySuites, proofProperty: 'signature',
purpose: null, documentLoader, expansionMap})
])));
if(results.length === 0) {
throw new Error(
'Could not verify any proofs; no proofs matched the required ' +
'purpose.');
'suite and purpose.');
}

// verify each matching proof
const results = (await Promise.all(matches.map(proof => {
for(const suite of suites) {
if(suite.match(proof)) {
return suite.verifyProof(
proof, {document, purpose, documentLoader, expansionMap});
}
}
return {
verified: false,
error: new Error('No matching suite found.')
};
}))).map((r, i) => ({proof: matches[i], ...r}));
const verified = results.some(r => r.verified);
if(!verified) {
const errors = results.filter(r => !r.verified).map(r => r.error);
return {verified, results, error: [].concat(...errors)};
const errors = [].concat(
...results.filter(r => r.error).map(r => r.error));
const result = {verified, results};
if(errors.length > 0) {
result.error = errors;
}
return result;
}
return {verified, results};
} catch(error) {
return {verified: false, error};
}
}
};

async function _verify({
document, suites, proofProperty, purpose, documentLoader, expansionMap}) {
if(suites.length === 0) {
return [];
}

const {[proofProperty]: proofSet} = document;
if(!proofSet) {
// no possible matches
return [];
}
delete document[proofProperty];

// compact proofs to security-v2 context
const expanded = {
[proofProperty]: proofSet
};
const ctx = jsonld.getValues(document, '@context');
expanded['@context'] = ctx;
const compact = await jsonld.compact(
expanded, constants.SECURITY_CONTEXT_URL, {documentLoader, expansionMap});

// filter out matching proofs
const proofs = jsonld.getValues(compact, proofProperty).map(proof => {
proof['@context'] = constants.SECURITY_CONTEXT_URL;
return proof;
});
const matches = proofs.filter(proof => purpose ?
purpose.match({proof, document, documentLoader, expansionMap}) :
true);
if(matches.length === 0) {
// no matches, nothing to verify
return [];
}

// verify each matching proof
return (await Promise.all(matches.map(proof => {
for(const s of suites) {
if(s.match({proof, document, documentLoader, expansionMap})) {
return s.verifyProof(
{proof, document, purpose, documentLoader, expansionMap})
.catch(error => {
return {verified: false, error};
});
}
}
}))).map((r, i) => r ? {proof: matches[i], ...r} : null).filter(r => r);
}
1 change: 1 addition & 0 deletions lib/contexts/security-v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = {
"publicKey": {"@id": "sec:publicKey", "@type": "@id"},
"publicKeyBase58": "sec:publicKeyBase58",
"publicKeyPem": "sec:publicKeyPem",
"publicKeyWif": "sec:publicKeyWif",
"publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
"revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
"salt": "sec:salt",
Expand Down
4 changes: 2 additions & 2 deletions lib/jsonld-signatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ api.sign = util.callbackify(async function sign(document, {
});

api.verify = util.callbackify(async function verify(document, {
suites, purpose, documentLoader, expansionMap} = {}) {
suite, purpose, documentLoader, expansionMap} = {}) {
return new ProofSet().verify(
document, {suites, purpose, documentLoader, expansionMap});
document, {suite, purpose, documentLoader, expansionMap});
});

/* Helper functions */
Expand Down
9 changes: 7 additions & 2 deletions lib/proof-purpose/ProofPurposeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ module.exports = class ProofPurposeHandler {
'"validate" must be implemented in a derived class.');
}

async updateProof({input, proof, purposeParameters, documentLoader}) {
async update({input, proof, purposeParameters, documentLoader}) {
throw new Error(
'"createProof" must be implemented in a derived class.');
'"update" must be implemented in a derived class.');
}

async match({proof, document, documentLoader, expansionMap}) {
throw new Error(
'"match" must be implemented in a derived class.');
}
};
15 changes: 0 additions & 15 deletions lib/sign.js

This file was deleted.

11 changes: 7 additions & 4 deletions lib/suites/EcdsaKoblitzSignature2016.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ module.exports = class EcdsaKoblitzSignature2016
throw new TypeError('"privateKeyWif" must be a base58 formatted string.');
}

const bitcoreMessage = require('bitcoreMessage');
const bitcoreMessage = require('bitcore-message');
const bitcore = bitcoreMessage.Bitcore;
const privateKey = bitcore.PrivateKey.fromWIF(this.privateKeyWif);
const message = bitcoreMessage(forge.util.binary.encode(verifyData));
const message = bitcoreMessage(forge.util.binary.raw.encode(verifyData));
proof.signatureValue = message.sign(privateKey);

return proof;
}

async verifySignature({verifyData, proof}) {
const bitcoreMessage = require('bitcoreMessage');
const message = bitcoreMessage(forge.util.binary.encode(verifyData));
const bitcoreMessage = require('bitcore-message');
const message = bitcoreMessage(forge.util.binary.raw.encode(verifyData));
return message.verify(this.publicKeyWif, proof.signatureValue);
}

Expand All @@ -46,6 +46,9 @@ module.exports = class EcdsaKoblitzSignature2016
'Unknown public key encoding. Public key encoding must be ' +
'"publicKeyWif".');
}
if(!this.publicKeyWif) {
this.publicKeyWif = verificationMethod.publicKeyWif;
}
return verificationMethod;
}
};
13 changes: 1 addition & 12 deletions lib/suites/GraphSignature2012.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,6 @@ module.exports = class GraphSignature2012 extends LinkedDataSignature2015 {
verifyData += '@' + proof.domain;
}
const buffer = new forge.util.ByteBuffer(verifyData, 'utf8');
return forge.util.binary.raw.encode(buffer.getBytes());
}

async getVerificationMethod({proof, documentLoader}) {
const verificationMethod = await super.getVerificationMethod(
{proof, documentLoader});
if(typeof verificationMethod.publicKeyPem !== 'string') {
throw new TypeError(
'Unknown public key encoding. Public key encoding must be ' +
'"publicKeyPem".');
}
return verificationMethod;
return forge.util.binary.raw.decode(buffer.getBytes());
}
};
Loading

0 comments on commit 3cbcc73

Please sign in to comment.