From 231466415fc5e68e5f423e21e12949b00fd3056b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 6 Sep 2018 11:29:17 +0100 Subject: [PATCH] feat: add records validator --- .gitignore | 1 + README.md | 14 ++++++++ src/index.js | 38 ++++++++++++++++++-- test/index.spec.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2e81726..85223e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist/ +docs/ # Logs logs diff --git a/README.md b/README.md index cd7df0e..3996f1c 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,20 @@ const data = ipns.unmarshal(storedData) Returns the entry data structure after being serialized. +#### Validator + +```js +const ipns = require('ipns') + +const validator = ipns.validator +``` + +Contains an object with `validate (marshalledData, peerId, callback)` and `select (dataA, dataB, callback)` functions. + +The `validate` function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (signature and validity are verified). + +The `select` function is responsible for deciding which ipns record is the best (newer) between two records. Both records are unmarshalled and their sequece numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`. + ## API #### Create record diff --git a/src/index.js b/src/index.js index 86950c4..cc5869a 100644 --- a/src/index.js +++ b/src/index.js @@ -257,6 +257,38 @@ const extractPublicKeyFromId = (peerId) => { return crypto.keys.unmarshalPublicKey(decodedId.digest) } +const marshal = ipnsEntryProto.encode + +const unmarshal = ipnsEntryProto.decode + +const validator = { + validate: (marshalledData, peerId, callback) => { + const receivedEntry = unmarshal(marshalledData) + + // extract public key + extractPublicKey(peerId, receivedEntry, (err, pubKey) => { + if (err) { + return callback(err) + } + + // Record validation + validate(pubKey, receivedEntry, (err) => { + if (err) { + return callback(err) + } + + callback(null, true) + }) + }) + }, + select: (dataA, dataB, callback) => { + const entryA = unmarshal(dataA) + const entryB = unmarshal(dataB) + + callback(null, entryA.sequence > entryB.sequence ? 0 : 1) + } +} + module.exports = { // create ipns entry record create, @@ -271,7 +303,9 @@ module.exports = { // get keys for routing getIdKeys, // marshal - marshal: ipnsEntryProto.encode, + marshal, // unmarshal - unmarshal: ipnsEntryProto.decode + unmarshal, + // validator + validator } diff --git a/test/index.spec.js b/test/index.spec.js index aded798..2a3caf4 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -234,4 +234,91 @@ describe('ipns', function () { }) }) }) + + it('should use validator.validate to validate a record', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(rsa.public, entry, (err, entry) => { + expect(err).to.not.exist() + + const marshalledData = ipns.marshal(entry) + + ipns.validator.validate(marshalledData, ipfsId, (err, valid) => { + expect(err).to.not.exist() + expect(valid).to.equal(true) + done() + }) + }) + }) + }) + + it('should use validator.validate to verify that a record is not valid', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.embedPublicKey(rsa.public, entry, (err, entry) => { + expect(err).to.not.exist() + + // corrupt the record by changing the value to random bytes + entry.value = crypto.randomBytes(46).toString() + const marshalledData = ipns.marshal(entry) + + ipns.validator.validate(marshalledData, ipfsId, (err) => { + expect(err).to.exist() // failed validation + done() + }) + }) + }) + }) + + it('should use validator.select to select the newer record returning 0 if it is the first parameter', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.create(rsa, cid, (sequence + 1), validity, (err, newEntry) => { + expect(err).to.not.exist() + + const marshalledData = ipns.marshal(entry) + const marshalledNewData = ipns.marshal(newEntry) + + ipns.validator.select(marshalledNewData, marshalledData, (err, valid) => { + expect(err).to.not.exist() + expect(valid).to.equal(0) // new data is the selected one + done() + }) + }) + }) + }) + + it('should use validator.select to select the newer record returning 1 if it is the second parameter', (done) => { + const sequence = 0 + const validity = 1000000 + + ipns.create(rsa, cid, sequence + 1, validity, (err, entry) => { + expect(err).to.not.exist() + + ipns.create(rsa, cid, (sequence), validity, (err, newEntry) => { + expect(err).to.not.exist() + + const marshalledData = ipns.marshal(entry) + const marshalledNewData = ipns.marshal(newEntry) + + ipns.validator.select(marshalledNewData, marshalledData, (err, valid) => { + expect(err).to.not.exist() + expect(valid).to.equal(1) // old data is the selected one + done() + }) + }) + }) + }) })