diff --git a/lib/feed-v1/validation.js b/lib/feed-v1/validation.js index f6a9c6d..1df2006 100644 --- a/lib/feed-v1/validation.js +++ b/lib/feed-v1/validation.js @@ -90,6 +90,22 @@ function validateTangle(msg, tangle, tangleId) { // prettier-ignore return new Error('invalid message: prev must be an array, on feed: ' + msg.metadata.who); } + if (typeof depth !== 'number') { + // prettier-ignore + return new Error('invalid message: depth must be a number, on feed: ' + msg.metadata.who); + } + if (tangle.isFeed()) { + const { type, who } = tangle.getFeed() + if (type !== msg.metadata.type) { + // prettier-ignore + return new Error(`invalid message: type "${msg.metadata.type}" does not match feed type "${type}"`) + } + if (who !== msg.metadata.who) { + // prettier-ignore + return new Error(`invalid message: who "${msg.metadata.who}" does not match feed who "${who}"`) + } + } + let minDiff = Infinity for (const p of prev) { if (typeof p !== 'string') { // prettier-ignore @@ -106,10 +122,16 @@ function validateTangle(msg, tangle, tangleId) { } const prevDepth = tangle.getDepth(p) - if (prevDepth >= depth) { + const diff = depth - prevDepth + if (diff <= 0) { // prettier-ignore return new Error('invalid message: depth of prev ' + p + ' is not lower, on feed: ' + msg.metadata.who); } + if (diff < minDiff) minDiff = diff + } + if (minDiff !== 1) { + // prettier-ignore + return new Error('invalid message: depth must be the largest prev depth plus one'); } } diff --git a/lib/tangle.js b/lib/tangle.js index 54c3508..1c98d0d 100644 --- a/lib/tangle.js +++ b/lib/tangle.js @@ -2,6 +2,10 @@ * @typedef {import("./plugin").Rec} Rec */ +/** + * @typedef {import("./feed-v1").Msg} Msg + */ + function lipmaa(n) { let m = 1 let po3 = 3 @@ -45,6 +49,11 @@ class Tangle { */ #rootHash + /** + * @type {Msg} + */ + #rootMsg + /** * @type {Set} */ @@ -89,6 +98,7 @@ class Tangle { this.#tips.add(msgHash) this.#perDepth.set(0, [msgHash]) this.#depth.set(msgHash, 0) + this.#rootMsg = msg } else if (tangles[this.#rootHash]) { this.#tips.add(msgHash) const prev = tangles[this.#rootHash].prev @@ -162,6 +172,18 @@ class Tangle { return this.#depth.get(msgHash) ?? -1 } + isFeed() { + if (this.#rootMsg.content) return false + const metadata = this.#rootMsg.metadata + return metadata.size === 0 && metadata.proof === '' + } + + getFeed() { + if (!this.isFeed()) return null + const { type, who } = this.#rootMsg.metadata + return { type, who } + } + #shortestPathToRoot(msgHash) { const path = [] let current = msgHash diff --git a/test/feed-v1-invalid-prev.test.js b/test/feed-v1-invalid-prev.test.js index d72860e..596043e 100644 --- a/test/feed-v1-invalid-prev.test.js +++ b/test/feed-v1-invalid-prev.test.js @@ -176,3 +176,56 @@ tape('invalid msg with unknown prev', (t) => { t.end() }) }) + +tape('invalid feed msg with a different who', (t) => { + const keysA = generateKeypair('alice') + const keysB = generateKeypair('bob') + + const rootMsg = FeedV1.createRoot(keysA, 'post') + const rootHash = FeedV1.getMsgHash(rootMsg) + const feedTangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + + const msg = FeedV1.create({ + keys: keysB, + content: { text: 'Hello world!' }, + type: 'post', + tangles: { + [rootHash]: feedTangle, + }, + when: 1652030002000, + }) + const msgHash = FeedV1.getMsgHash(msg) + + FeedV1.validate(msg, feedTangle, msgHash, rootHash, (err) => { + t.match(err.message, /who ".*" does not match feed who/, 'invalid feed msg') + t.end() + }) +}) + +tape('invalid feed msg with a different type', (t) => { + const keysA = generateKeypair('alice') + + const rootMsg = FeedV1.createRoot(keysA, 'post') + const rootHash = FeedV1.getMsgHash(rootMsg) + const feedTangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + + const msg = FeedV1.create({ + keys: keysA, + content: { text: 'Hello world!' }, + type: 'comment', + tangles: { + [rootHash]: feedTangle, + }, + when: 1652030002000, + }) + const msgHash = FeedV1.getMsgHash(msg) + + FeedV1.validate(msg, feedTangle, msgHash, rootHash, (err) => { + t.match( + err.message, + /type "comment" does not match feed type "post"/, + 'invalid feed msg' + ) + t.end() + }) +})