UNPKG

tiesdb-client

Version:
326 lines (282 loc) 9.59 kB
let ebml = require('universal-ebml'); let schema = require('./schema.js'); let etu = require('ethereumjs-util'); const createKeccakHash = require('keccak'); let encoder = new ebml.Encoder(null, schema); let encodedData; class Tag { constructor(propertiesOrName) { if(typeof propertiesOrName === 'string') { this.name = propertiesOrName; } else if(propertiesOrName) { for (let p in propertiesOrName) this[p] = propertiesOrName[p]; } if(!this.type && this.name) this.type = schema.findTagByName(this.name).type; Object.defineProperty(this, '__childrenMap', {enumerable: false, configurable: true, writable: true}); } /** * * @param name * @returns Tag */ getChild(name) { if(!this.children) return; if(typeof name === 'number') return this.children[name]; let c = this.__childrenMap[name]; return c && c[0]; } /** * * @param name * @returns [Tag] */ getChildren(name) { if(!this.children) return []; if(name) return this.__childrenMap[name] || []; return this.children; } addChild(tag, value) { if(typeof tag === 'string') { if(typeof value !== undefined) { if(Buffer.isBuffer(value)) tag = new Tag({name: tag, data: value}); else tag = new Tag({name: tag, value: value}); } else { tag = new Tag(tag); } }else if(!(tag instanceof Tag)){ throw new Error('Only Tag object can be child of Tag'); } let item = this, item1 = tag; if(!item.children) item.children = []; if(!item.__childrenMap) item.__childrenMap = {}; if(!item.__childrenMap[item1.name]) item.__childrenMap[item1.name] = []; item.__childrenMap[item1.name].push(item1); item.children.push(item1); } ensureData() { if(!this.data && this.type !== 'm') { ebml.tools.writeDataToTag(this, this.value); } } getValue() { if(this.value !== undefined) return this.value; if(this.type === 'm') return this.children; if(this.data) this.value = ebml.tools.toType(this.data, this.type); return this.value; } toString(format, indent = ' ', level = 0) { if(format === 'xml'){ let idnt = indent.repeat(level); let val; if(this.type === 'm') { let children = (this.children || []).map(tag => tag.toString(format, indent, level + 1)); val = `\n${children.join('\n')}\n${idnt}`; }else{ if(this.type == 'b') { val = this.getValue().toString('hex'); }else if(this.type == 'd') { val = this.getValue(); if(!(val instanceof Date)) val = new Date(+val); val = val.toISOString(); }else{ val = this.getValue(); } } return `${idnt}<${this.name} type="${this.type}">${val}</${this.name}>`; }else{ return super.toString(...arguments); } } } encoder.on('data', function(chunk) { encodedData = chunk; }); function decode(/*Buffer*/ data, myAddress) { let decoder = new ebml.Decoder(null, schema); let stack = [new Tag()]; decoder.on('data', function(chunk) { if(chunk[0] === 'start') { let item = stack[stack.length - 1]; let data = chunk[1]; let item1 = new Tag({ name: data.name, type: data.type }); item.addChild(item1); stack.push(item1); }else if(chunk[0] === 'tag') { let item = stack[stack.length - 1]; let data = chunk[1]; let item1 = new Tag({ name: data.name, type: data.type, data: data.data, value: data.value }); item.addChild(item1); }else{ //chunk[0] === 'end' stack.pop(); } }); decoder.write(data); if(decoder._tag_stack.length) throw new Error('Failed to parse: ' + data.toString('hex')); let obj = stack[0].children[0]; check(obj, myAddress); return obj; } function encode(raw) { function encodeInner(node){ let info = encoder._schema.findTagByName(node.name); if(info.type === 'm'){ encoder.write(['start', node]); for(let i=0; i<node.children.length; ++i){ encodeInner(node.children[i]); } encoder.write(['end', node]); }else{ node.ensureData(); encoder.write(['tag', node]); } } encodeInner(raw); return encodedData; } function check(obj, myAddress) { switch (obj.name) { case 'ModificationRequest': checkModificationRequest(obj, myAddress); break; } } function checkModificationRequest(obj, myAddress){ let entries = obj.getChildren('Entry'); for(let i=0; i<entries.length; ++i){ checkEntry(entries[i], myAddress); } } function checkEntry(entry, myAddress) { let header = entry.getChild('EntryHeader'); let hash = getTagHash(header); let addr = getSigner(header, hash); let signer = header.getChild('Signer').data; if(Buffer.compare(addr, signer) != 0) throw new Error('Entry signature check is failed! Sig: ' + header.getChild('Signature').data.toString('hex').substr(0, 20) + '...'); checkFields(entry.getChild('FieldList'), header.getChild('EntryFldHash')); if(myAddress) checkCheques(entry, myAddress); return hash; } function getTagHash(obj){ let children = obj.getChildren(); let keccak = createKeccakHash('keccak256'); for(let i=0; i<children.length; ++i){ let child = children[i]; if(child.name != 'Signature') computeHashOnData(child, keccak); } let hash = keccak.digest(); // console.log("Hash of data: " + hash.toString('hex')) return hash; } function getSigner(obj, hash) { let signature = obj.getChild('Signature').data; if(!hash) hash = getTagHash(obj); let v = signature[64]; if(v > 28) v -= 10; //ethereumjs-util supports only v in [27, 28] (EIP-155) v = v > 28 ? v - 10 : v; let pubk = etu.ecrecover(hash, v, signature.slice(0, 32), signature.slice(32, 64)); let addr = etu.pubToAddress(pubk); return addr; } function computeHashOnData(obj, hash) { if(obj.type == 'm') { let children = obj.getChildren(); for(let i=0; i<children.length; ++i) { hash = computeHashOnData(children[i], hash); } }else{ if(!hash) hash = createKeccakHash('keccak256'); obj.ensureData(); // console.log('Hashing ' + obj.name + ': ' + obj.data.toString('hex')); hash.update(obj.data); } return hash; } function computeFieldsHash(list) { let fields = list.getChildren('Field'); if(!fields) return; let keccakAllFields = createKeccakHash('keccak256'); for(let i=0; i<fields.length; ++i) { let field = fields[i]; let fldhash = field.getChild('FieldHash'); if(fldhash){ fldhash.ensureData(); keccakAllFields.update(fldhash.data); }else { let keccakField = createKeccakHash('keccak256'); let name = field.getChild('FieldName'); name.ensureData(); let value = field.getChild('FieldValue'); field.ensureData(); keccakField.update(name.data); keccakField.update(value.data); keccakAllFields.update(keccakField.digest()); } } return keccakAllFields.digest(); } function checkFields(list, hash){ let computed = computeFieldsHash(list); hash = hash.data; if(Buffer.compare(hash, computed) != 0) throw new Error('Fields hash does not match! Hash: ' + hash.toString('hex')); } function checkCheques(entry, myAddress) { let list = entry.getChild('ChequeList'); if(!list) return; let cheques = list.getChildren('Cheque'); for(let i=0; i<cheques.length; ++i) { let cheque = cheques[i]; let addr = getSigner(cheque); if(Buffer.compare(addr, myAddress) != 0) throw new Error(`Cheque is not mine: ${cheque.getChild('ChequeRange').data.toString('hex')}-${cheque.getChild('ChequeRange').value}`); } } function sign(msgHash, pk) { let sig = etu.secp256k1.sign(msgHash, pk); let sigBuf = Buffer.allocUnsafe(65); sig.signature.copy(sigBuf, 0, 0, 64); sigBuf[64] = sig.recovery + 37; //we need [37, 38] (EIP-155) return sigBuf; } module.exports = { decode, encode, checkEntry, Tag, computeHashOnData, computeFieldsHash, getSigner, sign, };