UNPKG

tim

Version:

Tradle's P2P OTR-based chat with plaintext and structured messages, and the ability to externalize messages onto a blockchain (currently bitcoin's)

150 lines (130 loc) 3.93 kB
var bitcoin = require('@tradle/bitcoinjs-lib') var map = require('map-stream') var Q = require('q') var debug = require('debug')('unchainer') var extend = require('xtend/mutable') var typeforce = require('typeforce') var Parser = require('@tradle/chained-obj').Parser var Identity = require('@tradle/identity').Identity var Verifier = require('@tradle/verifier') var CONSTANTS = require('@tradle/constants') var utils = require('./utils') var Errors = require('./errors') var TYPE = CONSTANTS.TYPE var CUR_HASH = CONSTANTS.CUR_HASH var ROOT_HASH = CONSTANTS.ROOT_HASH var SIGNEE = CONSTANTS.SIGNEE var plugins = Verifier.plugins var DEFAULT_PLUGINS = [ plugins.sigcheck, plugins.prev, plugins.identity ] /** * transactions go in, verified on-chain objects come out (or die inside) * @param {[type]} options [description] * @return {[type]} [description] */ module.exports = function chainstream (options) { typeforce({ lookup: 'Function', chainloader: 'Object' }, options) var chainloader = options.chainloader var verifier = new Verifier({ lookup: options.lookup }) var plugins = DEFAULT_PLUGINS.slice() if (options.plugins) { options.plugins.forEach(function (p) { if (plugins.indexOf(p) === -1) { plugins.push(p) } }) } verifier.use(plugins) return { unchain: unchain } function unchain (txEntry, opts) { opts = opts || {} typeforce({ parse: '?Boolean', verify: '?Boolean' }, opts) var doVerify = opts.verify !== false var doParse = doVerify || opts.parse !== false var processed = extend({}, txEntry) return chainloader.load(txEntry) .catch(saveProgressAndRethrow(processed)) .then(function (obj) { extend(processed, obj) if (doParse) { return parse(processed) .then(checkFrom.bind(null, processed)) } }) .then(function () { processed[CUR_HASH] = processed.key processed[ROOT_HASH] = processed.parsed.data[ROOT_HASH] || processed.key processed[TYPE] = processed.parsed.data[TYPE] if (doVerify) { return verify(verifier, processed) } }) .then(function () { return processed }) } } function parse (chainedObj) { return Parser.parse(chainedObj.data) .then(function (parsed) { chainedObj.parsed = parsed }) .catch(saveProgressAndRethrow(chainedObj, Errors.Parse)) } function checkFrom (chainedObj) { var parsed = chainedObj.parsed var json = parsed.data var isIdentity = json[TYPE] === Identity.TYPE var from = chainedObj.from // only identities are allowed to be created without an identity if (!from && isIdentity) { chainedObj.from = { identity: Identity.fromJSON(json) } chainedObj.from[ROOT_HASH] = chainedObj[ROOT_HASH] || chainedObj.key } if (!(chainedObj.from && chainedObj.to)) return // at this point, from/to are based on tx addresses // if possible, take them from signing party // not possible if an object was shared that was authored by a third party var objAuthor = chainedObj.parsed.data[SIGNEE] if (!objAuthor) return var objAuthorRH = objAuthor && objAuthor.split(':')[0] var txAuthorRH = chainedObj.from[ROOT_HASH] var toRH = chainedObj.to[ROOT_HASH] if (toRH === objAuthorRH) { // switch var tmp = chainedObj.from chainedObj.from = chainedObj.to chainedObj.to = tmp } } function verify (verifier, chainedObj) { return Q.ninvoke(verifier, 'verify', chainedObj) .catch(saveProgressAndRethrow(chainedObj, Errors.Verification)) } function saveProgressAndRethrow (chainedObj, ErrType) { return function (err) { debug('unchain failed', chainedObj.key, err.message) if (ErrType) err = new ErrType(err) utils.addError({ entry: chainedObj, group: 'unchain', error: err }) throw err } }