diff --git a/index.js b/index.js index f5f0495..47d9be4 100644 --- a/index.js +++ b/index.js @@ -157,6 +157,32 @@ exports.checkInvalid = function (state, hmac_key, msg) { return false //not invalid } +exports.checkInvalidBulk = function (state, hmac_key, messages) { + + // todo: validate all authors are ourself + + for (var i = 0; i < messages.length; i++) { + var message = messages[i].message + + if(!ref.isFeedId(message.author)) + return new Error('invalid message: must have author') + if(!isSigMatchesCurve(message)) + return new Error('invalid message: signature type must match author type') + + if(!isValidOrder(message, true)) + return fatal(new Error('message must have keys in allowed order')) + + var invalidShape = isInvalidShape(message) + + if (invalidShape) return invalidShape + + if(!ssbKeys.verifyObj({public: message.author.substring(1)}, hmac_key, message)) + return fatal(new Error('invalid signature')) + + } + +} + /* { //an array of messages which have been validated, but not written to the database yet. @@ -240,6 +266,53 @@ exports.append = function (state, hmac_key, msg) { return exports.appendKVT(state, hmac_key, exports.toKeyValueTimestamp(msg)) } +exports.appendBulk = function(state, hmac_key, messages) { + var err = exports.checkInvalidBulk(state, hmac_key, messages) + + if (err) throw err; + + var kvtMessages = messages.map(function (msg) { + return exports.toKeyValueTimestamp(msg.message, msg.id) + }); + + var firstMessage = kvtMessages[0]; + var lastMessage = kvtMessages[kvtMessages.length - 1] + + var msgAuthor = firstMessage.value.author; + var lowestSequence = firstMessage.value.sequence + + var highestSequence = lastMessage.value.sequence + var lastMessageId = lastMessage.value.key + + var timestamp = lastMessage.value.timestamp + + // Dequeue anything on the per-feed queue to the main queue before making the new write + if (state.feeds[msgAuthor]) { + var a = state.feeds[msgAuthor] + a.id = lastMessageId + a.sequence = highestSequence + a.timestamp = timestamp + var q = state.feeds[msgAuthor].queue + state.validated += q.length + state.queued -= q.length + for (var i = 0; i < q.length; ++i) + state.queue.push(q[i]) + q = [] + } else if (lowestSequence === 1) { + state.feeds[msgAuthor] = { + id: lastMessageId, + sequence: highestSequence, + timestamp: timestamp, + queue: [] + } + } + + state.queue.push(kvtMessages) + state.validated += 1 + + return state +} + exports.validate = function (state, hmac_key, feed) { if(!state.feeds[feed] || !state.feeds[feed].queue.length) { return state @@ -251,6 +324,7 @@ exports.validate = function (state, hmac_key, feed) { //pass in your own timestamp, so it's completely deterministic exports.create = function (state, keys, hmac_key, content, timestamp) { + if(timestamp == null || isNaN(+timestamp)) throw new Error('timestamp must be provided') if(!isObject(content) && !isEncrypted(content)) @@ -274,6 +348,61 @@ exports.create = function (state, keys, hmac_key, content, timestamp) { return ssbKeys.signObj(keys, hmac_key, msg) } +exports.createAll = function (state, keys, hmac_key, messages) { + + // The 'previous' for the first message in the bulk append will be the + // message currently at the head of the log or null if this is the first + // append + var previous = state ? state.id : null + + // The first sequence number is the head of the log's sequence number + 1, or 1 if this is the first + // append + var nextSequenceNumber = state ? state.sequence + 1 : 1 + var result = []; + + messages.forEach(function (message, idx) { + var content = message.content + var timestamp = message.timestamp + + // todo: Validate timestamps are increasing + + if(!isObject(content) && !isEncrypted(content)) { + throw new Error('invalid message content, must be object or encrypted string') + } + + var msg = { + previous: previous, + sequence: nextSequenceNumber, + author: keys.id, + timestamp: +timestamp, + hash: 'sha256', + content: content + } + + var err = isInvalidShape(msg) + if(err) throw err + + // We need to sign the message this stage to know what its ID is to use it as the 'previous' + // for the next message + var signedMsg = ssbKeys.signObj(keys, hmac_key, msg); + + var msgId = exports.id(signedMsg) + nextSequenceNumber = nextSequenceNumber + 1 + + // The next 'previous' for the next message in the bulk append will be this message + // once the message has been appended + previous = msgId + + // We return the ID with the signed message as well as the ID so we don't have to compute it again + result.push({ + message: signedMsg, + id: msgId + }) + }) + + return result; +} + exports.id = function (msg) { return '%'+ssbKeys.hash(JSON.stringify(msg, null, 2)) }