UNPKG

@nanocat/friday-serialized

Version:

Tx, Block and other data defenition for friday framework

487 lines (409 loc) 15.2 kB
const bitowl = require('bitowl'); module.exports = (app) => { class TX extends app.PRIMITIVE { constructor(data) { super(); this.type = 'build'; this.signed = false; this.hash = ''; this.version = app.config.txversion; this.inputs = []; this.outputs = []; this.data = ''; this.signdata = []; this.fee = 0; this.size = 0; this.coinbase = 0; this.merkle = ''; this.key = ''; this.isValidTransaction = 1; this.keystore = []; this.validation_errors = []; if (data) { if (!(data instanceof Buffer || typeof data == 'string')) throw new Error('TX:Constructor, data must be string (hex) or buffer instance'); this.hex = new Buffer(data, 'hex'); this.type = 'raw'; this.init(); } } init() { if (this.type == 'build') { //sign this.signTransaction(); //create hex } if (this.type == 'raw') { //decode hex this.fromJSON(bitowl.data.unpack(this.hex)) } if (this.type == 'json') { //create hex this.hex = this.toBuffer(); } //calculate fee,size,hash this.signed = this.verifyTransaction(); this.hash = this.getId(); this.size = this.hex.length; this.fee = 0; if (this.version > app.config.txversion) { this.emit("unsupportedversion", app.config.txversion, this.version); }//else is okay } setInputs(arr) { //setup signdata BEFORE this method for (let i in arr) { if (!arr[i].hash && arr[i].index != -1) throw new Error('field hash is not exist for input of new tx.in[' + i + ']'); if (arr[i].s) { this.signdata[i] = arr[i].s;//[p.der, p.publicKey] } if (!arr[i].prevAddress && this.signdata[i]) { arr[i].prevAddress = this.getAddressByPublicKey(this.signdata[i][1]); } if (!arr[i].prevAddress && this.keystore[i]) { arr[i].prevAddress = this.getAddressByPublicKey(this.keystore[i]); } if (!arr[i].prevAddress) throw new Error('field prevAddress is not entered for input of new tx.in[' + i + ']'); this.inputs[i] = arr[i] } return this; } setOutputs(arr) { let out = []; for (let i in arr) { if (arr[i].address) { if (!this.isValidAddress(arr[i].address)) throw new Error('invalid address field for tx.out[' + i + ']'); } let o = { amount: arr[i].amount, address: arr[i].address, } if (arr[i].key) o.key = arr[i].key; out.push(o); } this.outputs = out; return this; } setMerkle(merkle) { this.merkle = merkle; } setPublicKey(key) { this.key = key; } setCoinbase(coinbaseData) { this.coinbase = coinbaseData; } toJSON(rules) { if (!rules) rules = ""; let in_ = []; for (let i in this.inputs) { in_[i] = this.inputs[i]; if (in_[i].prevAddress) delete in_[i].prevAddress; } let outs_ = []; for (let i in this.outputs) { let temp = { address: this.outputs[i].address, amount: this.outputs[i].amount, } if (this.outputs[i].key && this.coinbase) temp.key = this.outputs[i].key; outs_.push(temp); } let o = { v: this.version, s: this.signdata, in: in_, out: outs_ } if (rules.split(",").indexOf('hash') != -1) { o.hash = this.getId(); } if (this.data) { o.ds = this.data; } if (this.coinbase) { o.cb = this.coinbase; o.m = this.merkle; o.k = this.key; delete o.in; } return o; } fromJSON(jsondata, additionalInfoRulesArray) { if (!additionalInfoRulesArray) additionalInfoRulesArray = []; this.type = 'json'; this.setVersion(jsondata.v); this.signdata = jsondata.s; this.setInputs(jsondata.in); this.setOutputs(jsondata.out); if (jsondata.ds) this.setData(jsondata.ds); if (jsondata.cb) { this.setCoinbase(jsondata.cb); this.setMerkle(jsondata.m); this.setPublicKey(jsondata.k); if (!jsondata.in) this.setInputs([{ index: -1 }]); } for (let i in additionalInfoRulesArray) { this[additionalInfoRulesArray[i]] = jsondata[additionalInfoRulesArray[i]]; } this.init(); return this; } fromHex(hexOrBuffer) { this.type = 'raw'; this.hex = new Buffer(hexOrBuffer, 'hex'); this.init(); return this; } setKeystore(keys) { this.keystore = keys; return this; } setVersion(version) { this.version = version; return this; } setData(data) { //datascript let dsc = ""; if (data instanceof Array && data.length > 0) { let scriptslist = []; for (let i in data) { if (data[i] instanceof dscript) scriptslist.push(data[i].toHEX()); else scriptslist.push(data[i]); } dsc = dscript.writeArray(scriptslist); } else dsc = data; this.data = dsc; return this; } toHex() { return this.hex.toString('hex'); } toBuffer(action) { for (let i in this.inputs) { if (this.type != 'build' && action != 'verify') if (!this.signdata[i]) throw new Error('Signature for input [' + i + '] doesnt exist'); } let d = this.toJSON(); if (action == 'verify') delete d.s;//sign is not part of txhash return bitowl.data.pack(d); } getId(forse) { if (!this.hash || forse) { this.hash = this.createHash(this.toBuffer('verify')).toString('hex'); } return this.hash; } getHash(forse) { return this.getId(forse); } getFee() { //calculate fee if (!this.coinbase && !this.fee) { let outval = 0, inval = 0; for (let i in this.inputs) { if (this.inputs[i].hash && this.inputs[i].index != -1) {//not a coinbase let out = this.getOut(this.inputs[i].hash, this.inputs[i].index); inval += out.amount; } } for (let i in this.outputs) { outval += this.outputs[i].amount; } this.fee = inval - outval; } return this.fee; } getSize() { //return size of tx return this.size; } isCoinbase() { return !!this.coinbase; } getOutputs() { return this.outputs } getInputs() { return this.inputs } send() { throw new Error('TX::send must be implemented'); } isValid(context) { this.emit("beforevalidation", context); let validator = new app.TX.VALIDATOR(this, context); let res = validator.isValid(); if (!res) this.validation_errors = validator.getErrors(); console.log('validator errors', this.validation_errors); this.emit("aftervalidation", res, validator.getLog(), validator.getErrors()); return res; } getLastErrorCodes() { return this.validation_errors; } signTransaction(private_keys) { //sign current data if (!private_keys) private_keys = this.keystore; if (!private_keys || (!private_keys instanceof Array) || private_keys.length < this.inputs.length) throw new Error('Invalid keystore length, must be >=' + this.inputs.length + ' keys'); let siglist = []; let txb = this.toBuffer('verify'), hash = this.createHash(txb); for (let i in this.inputs) { siglist[i] = [ this.sign(private_keys[i], new Buffer(hash, 'hex')).toString('hex'), this.getPublicKeyByPrivateKey(private_keys[i]) ]; } this.signdata = siglist; this.hex = this.toBuffer(); } verifyTransaction() { let res = []; let signable = this.toBuffer('verify'); let hash2sign = this.createHash(new Buffer(signable, 'hex')); for (let i in this.inputs) { let pubkey = this.signdata[i][1]; let sign = this.signdata[i][0]; res[i] = this.verify(pubkey, sign, new Buffer(hash2sign, 'hex')); //sometimes one of signs in big tx - can not be verified - so, its thrown error, TODO: check EC and find this bug (signs and messagehash is equal) } let result = true; for (let i in res) { if (!res[i]) result = false; } if (!result) throw new Error('can not verify signature of transaction'); return result; } } TX.createFromJSON = function (data, keys) { let incnt = 1; if (!data.cb) { if (!data.in) throw new Error('invalid txdata format, must exist fields txdata.in[]'); incnt = data.in.length } else { if (!data.k || !data.m || !data.cb) throw new Error('invalid coinbase tx data format, must exist fields txdata.k, txdata.m, txdata.cb'); if (!data.in || data.in.length == 0) data.in = [{ index: -1 }]; } if (!keys || keys.length < incnt) throw new Error("at least " + incnt + " keys must exist"); if (!data.out) throw new Error('invalid txdata format, must exist fields txdata.out[]'); if (!(data.out instanceof Array) || data.out.length < 1) throw new Error("at least one input must be in tx.out"); return app.TX.createFromRaw(data.in, data.out, keys, data.v, data.ds, { merkle: data.m, key: data.k, coinbase: data.cb }); } TX.createFromRaw = function (inputs, outputs, keys, version, ds, coinbaseData) { let tx = new app.TX(); if (!version) version = 1; tx .setVersion(version) .setKeystore(keys) .setInputs(inputs) .setOutputs(outputs) if (ds) tx.setData(ds); if (coinbaseData) { tx.setPublicKey(coinbaseData.key); tx.setMerkle(coinbaseData.merkle); tx.setCoinbase(coinbaseData.coinbase); } tx.init(); return tx; } TX.createCoinbase = function (fee, coinbaseBytes, privateKey, validatorsList, height) { if (!fee) fee = 0; let temp = new app.TX(); let fullamount = temp.getBlockValue(fee, height + 1); let outs = temp.createCoinbaseOutputs(privateKey, fullamount, validatorsList); return TX.createFromJSON({ v: app.config.txversion, out: outs, m: temp.createMerkle(validatorsList), k: temp.getPublicKeyByPrivateKey(privateKey), cb: new Buffer(coinbaseBytes, 'hex').toString('hex') }, [privateKey]); } TX.fromJSON = function (data) { return new app.TX().fromJSON(data); } TX.fromHEX = function (hex) { return new app.TX(hex); } TX.validate = function (tx, context) { return tx.isValid(context); } class validator { constructor(tx, context) { this.tx = tx; this.context = context; this.errors = []; this.log = []; } addError(msg, code) { this.errors.push({ message: msg, code: code }); return false; } getErrors() { return this.errors; } getLog() { return this.log; } isValid() { let res = 0, err = []; for (let i in validator.rules) { try { let r = validator.rules[i].apply(this.tx, [this, this.context || {}]); this.log.push({ 'action': i, 'status': r }); if (r) res += 1; } catch (e) { this.errors.push({ code: e.code, message: e.message, exception: true }); } } if (app.config.validationalert) { for (let k in this.errors) { this.tx.throwError(this.errors[k].message, this.errors[k].code); } } return res == Object.keys(validator.rules).length; } static addRule(name, fnc) { validator.rules[name] = fnc; } } validator.rules = {}; TX.VALIDATOR = validator; return TX }