mirror of https://codeberg.org/pzp/pzp-db.git
refactor validation
This commit is contained in:
parent
0beb2477a2
commit
f1a13eee80
53
lib/index.js
53
lib/index.js
|
@ -255,6 +255,30 @@ function initDB(peer, config) {
|
||||||
return tangles
|
return tangles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find which pubkeys are authorized to sign this msg given the account.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {DBTangle | null} accountTangle
|
||||||
|
* @returns {Set<string>}
|
||||||
|
*/
|
||||||
|
function getPubkeysInAccount(accountTangle) {
|
||||||
|
const pubkeys = new Set()
|
||||||
|
if (!accountTangle) return pubkeys
|
||||||
|
// TODO: prune the accountTangle beyond msg.metadata.accountTips
|
||||||
|
for (const msgID of accountTangle.topoSort()) {
|
||||||
|
const msg = get(msgID)
|
||||||
|
if (!msg?.data) continue
|
||||||
|
/** @type {AccountData} */
|
||||||
|
const data = msg.data
|
||||||
|
if (data.action !== 'add') continue
|
||||||
|
if (data.add.key.purpose !== 'sig') continue
|
||||||
|
if (data.add.key.algorithm !== 'ed25519') continue
|
||||||
|
pubkeys.add(data.add.key.bytes)
|
||||||
|
}
|
||||||
|
return pubkeys
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {CB<void>} cb
|
* @param {CB<void>} cb
|
||||||
*/
|
*/
|
||||||
|
@ -280,29 +304,24 @@ function initDB(peer, config) {
|
||||||
// TODO: optimize this. This may be slow if you're adding many msgs in a
|
// TODO: optimize this. This may be slow if you're adding many msgs in a
|
||||||
// row, because it creates a new Map() each time. Perhaps with QuickLRU
|
// row, because it creates a new Map() each time. Perhaps with QuickLRU
|
||||||
const tangle = new DBTangle(tangleID, records())
|
const tangle = new DBTangle(tangleID, records())
|
||||||
|
if (msgID === tangleID) {
|
||||||
|
tangle.add(msgID, msg)
|
||||||
|
}
|
||||||
|
|
||||||
// Find which pubkeys are authorized to sign this msg given the account:
|
// Identify the account and its pubkeys:
|
||||||
const pubkeys = new Set()
|
|
||||||
const accountID = getAccountID(rec)
|
const accountID = getAccountID(rec)
|
||||||
let accountTangle = /** @type {DBTangle | null} */ (null)
|
let accountTangle = /** @type {DBTangle | null} */ (null)
|
||||||
if (accountID && !MsgV3.isRoot(msg)) {
|
if (accountID) {
|
||||||
accountTangle = new DBTangle(accountID, records())
|
accountTangle = new DBTangle(accountID, records())
|
||||||
|
if (msgID === accountID) {
|
||||||
|
accountTangle.add(msgID, msg)
|
||||||
|
}
|
||||||
if (!accountTangle.has(accountID)) {
|
if (!accountTangle.has(accountID)) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return cb(new Error('add() failed because the account tangle is unknown'))
|
return cb(new Error('add() failed because the account tangle is unknown'))
|
||||||
}
|
}
|
||||||
// TODO: prune the accountTangle beyond msg.metadata.accountTips
|
|
||||||
for (const msgID of accountTangle.topoSort()) {
|
|
||||||
const msg = get(msgID)
|
|
||||||
if (!msg?.data) continue
|
|
||||||
/** @type {AccountData} */
|
|
||||||
const data = msg.data
|
|
||||||
if (data.action !== 'add') continue
|
|
||||||
if (data.add.key.purpose !== 'sig') continue
|
|
||||||
if (data.add.key.algorithm !== 'ed25519') continue
|
|
||||||
pubkeys.add(data.add.key.bytes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const pubkeys = getPubkeysInAccount(accountTangle)
|
||||||
|
|
||||||
let err
|
let err
|
||||||
if ((err = MsgV3.validate(msg, tangle, pubkeys, msgID, tangleID))) {
|
if ((err = MsgV3.validate(msg, tangle, pubkeys, msgID, tangleID))) {
|
||||||
|
@ -310,7 +329,11 @@ function initDB(peer, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account tangle related validations
|
// Account tangle related validations
|
||||||
if (msg.metadata.account === ACCOUNT_SELF && accountTangle) {
|
if (
|
||||||
|
msg.metadata.account === ACCOUNT_SELF &&
|
||||||
|
accountTangle &&
|
||||||
|
!MsgV3.isRoot(msg)
|
||||||
|
) {
|
||||||
/** @type {AccountData} */
|
/** @type {AccountData} */
|
||||||
const data = msg.data
|
const data = msg.data
|
||||||
if (data.action === 'add') {
|
if (data.action === 'add') {
|
||||||
|
|
|
@ -188,7 +188,7 @@ class Tangle {
|
||||||
return this.#depth.get(msgID) ?? -1
|
return this.#depth.get(msgID) ?? -1
|
||||||
}
|
}
|
||||||
|
|
||||||
isFeed() {
|
#isFeed() {
|
||||||
if (!this.#rootMsg) {
|
if (!this.#rootMsg) {
|
||||||
console.trace('Tangle is missing root message')
|
console.trace('Tangle is missing root message')
|
||||||
return false
|
return false
|
||||||
|
@ -197,7 +197,7 @@ class Tangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
get mootDetails() {
|
get mootDetails() {
|
||||||
if (!this.isFeed()) return null
|
if (!this.#isFeed()) return null
|
||||||
if (!this.#rootMsg) {
|
if (!this.#rootMsg) {
|
||||||
console.trace('Tangle is missing root message')
|
console.trace('Tangle is missing root message')
|
||||||
return null
|
return null
|
||||||
|
@ -206,6 +206,18 @@ class Tangle {
|
||||||
return { account, domain, id: this.#rootID }
|
return { account, domain, id: this.#rootID }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {'feed' | 'account' | 'weave'}
|
||||||
|
*/
|
||||||
|
get type() {
|
||||||
|
if (!this.#rootMsg) {
|
||||||
|
throw new Error('Tangle is missing root message')
|
||||||
|
}
|
||||||
|
if (this.#isFeed()) return 'feed'
|
||||||
|
if (this.#rootMsg.metadata.account === 'self') return 'account'
|
||||||
|
return 'weave'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} msgID
|
* @param {string} msgID
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,10 +6,15 @@ const stringify = require('json-canon')
|
||||||
const Tangle = require('./tangle')
|
const Tangle = require('./tangle')
|
||||||
const representData = require('./represent-data')
|
const representData = require('./represent-data')
|
||||||
const isMoot = require('./is-moot')
|
const isMoot = require('./is-moot')
|
||||||
const { SIGNATURE_TAG_MSG_V3, ACCOUNT_SELF } = require('./constants')
|
const {
|
||||||
|
SIGNATURE_TAG_MSG_V3,
|
||||||
|
ACCOUNT_SELF,
|
||||||
|
ACCOUNT_ANY,
|
||||||
|
} = require('./constants')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('.').Msg} Msg
|
* @typedef {import('.').Msg} Msg
|
||||||
|
* @typedef {import('.').AccountData} AccountData
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,20 +85,29 @@ function validatePubkey(msg) {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Msg} msg
|
* @param {Msg} msg
|
||||||
|
* @param {Tangle} tangle
|
||||||
* @param {Set<string>} pubkeys
|
* @param {Set<string>} pubkeys
|
||||||
* @returns {string | undefined}
|
* @returns {string | undefined}
|
||||||
*/
|
*/
|
||||||
function validateAccountPubkey(msg, pubkeys) {
|
function validatePubkeyAndAccount(msg, tangle, pubkeys) {
|
||||||
// Unusual case: if the msg is a feed root, ignore the account and pubkey
|
if (tangle.type === 'feed' || tangle.type === 'weave') {
|
||||||
if (isMoot(msg)) return
|
if (msg.metadata.account === ACCOUNT_SELF) {
|
||||||
|
// prettier-ignore
|
||||||
if (
|
return `invalid message: account "${msg.metadata.account}" cannot be "self" in a feed tangle\n` + JSON.stringify(msg)
|
||||||
msg.metadata.account &&
|
}
|
||||||
msg.metadata.account !== ACCOUNT_SELF &&
|
if (msg.metadata.account !== ACCOUNT_ANY && !pubkeys.has(msg.pubkey)) {
|
||||||
!pubkeys.has(msg.pubkey)
|
// prettier-ignore
|
||||||
) {
|
return `invalid message: pubkey "${msg.pubkey}" should have been one of "${[...pubkeys]}" from the account "${msg.metadata.account}"\n` + JSON.stringify(msg)
|
||||||
// prettier-ignore
|
}
|
||||||
return `invalid message: pubkey "${msg.pubkey}" should have been one of "${[...pubkeys]}" from the account "${msg.metadata.account}"\n` + JSON.stringify(msg)
|
} else if (tangle.type === 'account') {
|
||||||
|
if (msg.metadata.account !== ACCOUNT_SELF) {
|
||||||
|
// prettier-ignore
|
||||||
|
return `invalid message: account "${msg.metadata.account}" should have been "self" in an account tangle\n` + JSON.stringify(msg)
|
||||||
|
}
|
||||||
|
if (msg.metadata.accountTips !== null) {
|
||||||
|
// prettier-ignore
|
||||||
|
return `invalid message: accountTips "${msg.metadata.accountTips}" should have been null in an account tangle\n` + JSON.stringify(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,18 +127,6 @@ function validateMsgID(str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Msg} msg
|
|
||||||
* @returns {string | undefined}
|
|
||||||
*/
|
|
||||||
function validateDataSize(msg) {
|
|
||||||
const { dataSize } = msg.metadata
|
|
||||||
if (!Number.isSafeInteger(dataSize) || dataSize < 0) {
|
|
||||||
// prettier-ignore
|
|
||||||
return `invalid message: dataSize ${dataSize} should have been an unsigned integer\n` + JSON.stringify(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Msg} msg
|
* @param {Msg} msg
|
||||||
* @returns {string | undefined}
|
* @returns {string | undefined}
|
||||||
|
@ -184,7 +186,7 @@ function validateTangle(msg, tangle, tangleID) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return `invalid message: depth "${depth}" should have been a positive integer\n` + JSON.stringify(msg)
|
return `invalid message: depth "${depth}" should have been a positive integer\n` + JSON.stringify(msg)
|
||||||
}
|
}
|
||||||
if (tangle.isFeed()) {
|
if (tangle.type === 'feed') {
|
||||||
const { account, domain } = /** @type {MootDetails} */ (tangle.mootDetails)
|
const { account, domain } = /** @type {MootDetails} */ (tangle.mootDetails)
|
||||||
if (domain !== msg.metadata.domain) {
|
if (domain !== msg.metadata.domain) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -302,7 +304,17 @@ function validateData(msg) {
|
||||||
JSON.stringify(msg)
|
JSON.stringify(msg)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const [dataHash, dataSize] = representData(data)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
*/
|
||||||
|
function validateDataSizeHash(msg) {
|
||||||
|
const [dataHash, dataSize] = representData(msg.data)
|
||||||
|
if (!Number.isSafeInteger(dataSize) || dataSize < 0) {
|
||||||
|
// prettier-ignore
|
||||||
|
return `invalid message: dataSize ${dataSize} should have been an unsigned integer\n` + JSON.stringify(msg)
|
||||||
|
}
|
||||||
if (dataHash !== msg.metadata.dataHash) {
|
if (dataHash !== msg.metadata.dataHash) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return `invalid message: data hash "${dataHash}" does not match metadata.dataHash "${msg.metadata.dataHash}"\n` + JSON.stringify(msg)
|
return `invalid message: data hash "${dataHash}" does not match metadata.dataHash "${msg.metadata.dataHash}"\n` + JSON.stringify(msg)
|
||||||
|
@ -324,11 +336,14 @@ function validate(msg, tangle, pubkeys, msgID, rootID) {
|
||||||
let err
|
let err
|
||||||
if ((err = validateShape(msg))) return err
|
if ((err = validateShape(msg))) return err
|
||||||
if ((err = validatePubkey(msg))) return err
|
if ((err = validatePubkey(msg))) return err
|
||||||
if ((err = validateDataSize(msg))) return err
|
|
||||||
if ((err = validateData(msg))) return err
|
if ((err = validateData(msg))) return err
|
||||||
|
|
||||||
|
if (tangle.type === 'feed' && isMoot(msg)) return // nothing else to check
|
||||||
|
|
||||||
|
if ((err = validateDataSizeHash(msg))) return err
|
||||||
if ((err = validateDomain(msg.metadata.domain))) return err
|
if ((err = validateDomain(msg.metadata.domain))) return err
|
||||||
if ((err = validateAccountPubkey(msg, pubkeys))) return err
|
if ((err = validatePubkeyAndAccount(msg, tangle, pubkeys))) return err
|
||||||
if (tangle.size === 0) {
|
if (msgID === rootID) {
|
||||||
if ((err = validateTangleRoot(msg, msgID, rootID))) return err
|
if ((err = validateTangleRoot(msg, msgID, rootID))) return err
|
||||||
} else {
|
} else {
|
||||||
if ((err = validateTangle(msg, tangle, rootID))) return err
|
if ((err = validateTangle(msg, tangle, rootID))) return err
|
||||||
|
|
|
@ -13,6 +13,7 @@ test('validate root msg', (t) => {
|
||||||
const moot = MsgV3.createMoot(account, 'post', keypair)
|
const moot = MsgV3.createMoot(account, 'post', keypair)
|
||||||
const mootID = MsgV3.getMsgID(moot)
|
const mootID = MsgV3.getMsgID(moot)
|
||||||
const tangle = new MsgV3.Tangle(mootID)
|
const tangle = new MsgV3.Tangle(mootID)
|
||||||
|
tangle.add(mootID, moot)
|
||||||
|
|
||||||
const err = MsgV3.validate(moot, tangle, pubkeys, mootID, mootID)
|
const err = MsgV3.validate(moot, tangle, pubkeys, mootID, mootID)
|
||||||
assert.ifError(err, 'valid root msg')
|
assert.ifError(err, 'valid root msg')
|
||||||
|
@ -28,6 +29,7 @@ test('validate account tangle', (t) => {
|
||||||
const accountMsg0ID = account
|
const accountMsg0ID = account
|
||||||
|
|
||||||
const tangle = new MsgV3.Tangle(account)
|
const tangle = new MsgV3.Tangle(account)
|
||||||
|
tangle.add(accountMsg0ID, accountMsg0)
|
||||||
|
|
||||||
let err = MsgV3.validate(
|
let err = MsgV3.validate(
|
||||||
accountMsg0,
|
accountMsg0,
|
||||||
|
|
Loading…
Reference in New Issue