Skip to content

ssbc/ssb-validate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ssb-validate

validate ssb messages, completely functionally.

this module seeks to replace ssb-feed, and contain the logic to validate ssb messages, but this is implemented functionally, with serializable state. this means, that the states generated when the reference implementation runs can be extracted and used as test cases in other implementations.

example

var validate = require('ssb-validate')
var hmac_key = null
var state = validate.initial()

var msgs = [...] //some source of messages

msgs.forEach(function (msg) {
  try {
    state = validate.append(state, hmac_key, msg)
  } catch (err) {
    console.log(err)
  }
})

writeToDatabase(state.queue, function (err) {
  if(err) throw err

  //these messages are fully accepted now, can remove them from state.
  state.queue = []
})

//state should be saved in some way it can be reconstructed
//so in the future it can be appended to starting from scratch.

architecture

This module describes the validation logic for ssb messages, using a reduce style pattern - state = method(state, argument) . To make it easy to test, the various subcomponents are also exported, but the main method is state = append(state, hmac_key, msg)

state

this module uses a data structure to represent the state of validation. The structure is as follows:

{
  validated: integer,
  queued: integer,
  queue: [kvt...], //messages which have been validated and may now be written to database
  feeds: { //validation state of all feeds
    <feed_id>: {
      id: <msgId: key of last message>,
      timestamp: <timestamp of last message>,
      sequence: integer //
      queue: [kvt...]
    },...
  }
}

note: kvt here represents a msg with key calculated and a recieved timestamp assigned {key: hash(msg), value: msg, timestamp: timestamp()}

api

state = validate.append(state, hmac_key, msg)

Append and validate a message to state. If the message is valid, it will appear at the end of state.queue it may now be written to the database.

hmac_key is optional - if provided, messages are hmac'd with it before signing, which can be used to create a separate network in which messages cannot be copied to the main network. Messages compatible with the main network have hmac_key = null

msg = validate.create(feed_state, keys, hmac_key, content, timestamp)

Create a message that is valid given the current state of the feed (such that it may be passed to append(state, hmac_key, msg). keys is the signing key, as provided by ssbKeys.generate() or ssbKeys.createOrLoadSync(filename)

msg_id = validate.id(msg)

Calculate the message id for a given message.

shortcuts for js mode api

In investigating the possiblity of an entirely web based scuttlebutt, verifying all signatures in javascript had added a lot of overhead. (although now this is not such a problem because of crypto in fast-enough webassembly)

The following methods queues some number of messages and then validates the last signature. It is possible that a specially constructed messages with some invalid signatures, followed by messages with valid signatures could get accepted, but the writer doesn't have any way to know which messages will be accepted, and a 3rd party could not insert invalid messages into another feed, because the signature that is eventually checked wouldn't point to the right previous hash.

However, you can probably consider these methods not necessary. And they could be removed.

var validate = require('ssb-validate')
var hmac_key = null
var state = validate.initial()

var msgs = [...] //some source of messages

//queue messages
msgs.forEach(function (msg) {
  state = validate.queue(state, msg)
  if(state.error) console.error(state.error)
})

//validate messages

for(var feed_id in state.feeds)
  state = validate.validate(state, hmac_key, feed_id)

writeToDatabase(state.queue, function (err) {
  if(err) throw err

  //these messages are fully accepted now, can remove them from state.
  state.queue = []
})

//state should be saved in some way it can be reconstructed
//so in the future it can be appended to starting from scratch.

state = validate.queue(state, msg)

Call checkInvalidCheap and if valid, append to the feed's incoming queue. (state.feeds[id(msg)].queue)

The message is checked to have an incrementing sequence, and correct previous hash, but signature is not checked. (the intention here is for when using javascript crypto, checking every signature is expensive) however, this is not a big problem with wasm crypto.

state = validate.validate(state, feed_id)

Check the signature of the last message in feed_id's incoming queue, and if it is valid, append all messages in that queue. As optimization/shortcut for javascript crypto.

state = validate.appendOOO(state, hmac_key, msg)

This method works the same as append except that it does not check the previous link of the message. This allows one to validate an out of order message or a row of messages not starting from the beginning.

internal api

The following methods are exposed for testing, but are unlikely to be used directly.

state = validate.appendNew(state,hmac_key, keys, content, timestamp)

Wrapper around create and append. used in testing.

state = validate.appendKVT (state, hmac_key, kvt)

Internal details of append - recently this was refactored to avoid calculating the message id twice.

isInvalid = validate.checkInvalidCheap (state, msg)

Perform cheap checks for message validity, but not the signature. return false if the message is valid, and an error (with message) if it's invalid.

isInvalid = validate.checkInvalid (state, hmac_key, msg)

Check signature, returns false if message is valid. returns an error (with message) if the message is invalid.

License

MIT