UNPKG

aion-rlp

Version:

Aion Flavoured Recursive Length Prefix Encoding Module

298 lines (256 loc) 7.35 kB
const assert = require('assert') const Buffer = require('safe-buffer').Buffer const BN = require('bn.js') const JAVA_LONG_MAX = new BN('9223372036854775807') const MASK = new BN('4294967295') const INT_MASK = MASK //* --- AION LONG --- */ function AionLong (n) { 'use strict' const _this = this if (!(_this instanceof AionLong)) { // allow constructor call without new return new AionLong(n) } if (n === null || typeof n === 'undefined' || !('toArray' in n)) { throw new Error('unsupported input type') } if (new BN(n.toArray()).cmp(JAVA_LONG_MAX) > 0) { throw new Error('violated upper bound') } this.buf = n.toArray() } AionLong.prototype._aionLong = true AionLong.prototype.toArray = function() { return this.buf } AionLong.isAionLong = (a) => { if (a instanceof AionLong) { return true } return a !== null && typeof a === 'object' && a._aionLong === true } AionLong._aionEncodeLong = (bn) => { const top = bn.shrn(32).and(MASK) const bottom = bn.and(MASK) const buf = Buffer.alloc(8) buf.writeUInt32BE(top.toNumber(), 0) buf.writeUInt32BE(bottom.toNumber(), 4) return buf } AionLong.aionEncodeLong = (aionLong) => { const bn = new BN(aionLong.buf) if (bn.and(INT_MASK).cmp(bn) === 0) { return Buffer.from(bn.toArray()) } // otherwise this must be a long return AionLong._aionEncodeLong(bn) } exports.AionLong = AionLong /** * RLP Encoding based on: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP * This function takes in a data, convert it to buffer if not, and a length for recursion * * @param {Buffer,String,Integer,Array} data - will be converted to buffer * @returns {Buffer} - returns buffer of encoded data **/ exports.encode = function (input) { if (input instanceof Array) { var output = [] for (var i = 0; i < input.length; i++) { output.push(exports.encode(input[i])) } var buf = Buffer.concat(output) return Buffer.concat([encodeLength(buf.length, 192), buf]) } else { input = toBuffer(input) if (input.length === 1 && input[0] < 128) { return input } else { return Buffer.concat([encodeLength(input.length, 128), input]) } } } function safeParseInt (v, base) { if (v.slice(0, 2) === '00') { throw (new Error('invalid RLP: extra zeros')) } return parseInt(v, base) } function encodeLength (len, offset) { if (len < 56) { return Buffer.from([len + offset]) } else { var hexLength = intToHex(len) var lLength = hexLength.length / 2 var firstByte = intToHex(offset + 55 + lLength) return Buffer.from(firstByte + hexLength, 'hex') } } /** * RLP Decoding based on: {@link https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP|RLP} * @param {Buffer,String,Integer,Array} data - will be converted to buffer * @returns {Array} - returns decode Array of Buffers containg the original message **/ exports.decode = function (input, stream) { if (!input || input.length === 0) { return Buffer.from([]) } input = toBuffer(input) var decoded = _decode(input) if (stream) { return decoded } assert.equal(decoded.remainder.length, 0, 'invalid remainder') return decoded.data } exports.getLength = function (input) { if (!input || input.length === 0) { return Buffer.from([]) } input = toBuffer(input) var firstByte = input[0] if (firstByte <= 0x7f) { return input.length } else if (firstByte <= 0xb7) { return firstByte - 0x7f } else if (firstByte <= 0xbf) { return firstByte - 0xb6 } else if (firstByte <= 0xf7) { // a list between 0-55 bytes long return firstByte - 0xbf } else { // a list over 55 bytes long var llength = firstByte - 0xf6 var length = safeParseInt(input.slice(1, llength).toString('hex'), 16) return llength + length } } function _decode (input) { var length, llength, data, innerRemainder, d var decoded = [] var firstByte = input[0] if (firstByte <= 0x7f) { // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. return { data: input.slice(0, 1), remainder: input.slice(1) } } else if (firstByte <= 0xb7) { // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string // The range of the first byte is [0x80, 0xb7] length = firstByte - 0x7f // set 0x80 null to 0 if (firstByte === 0x80) { data = Buffer.from([]) } else { data = input.slice(1, length) } if (length === 2 && data[0] < 0x80) { throw new Error('invalid rlp encoding: byte must be less 0x80') } return { data: data, remainder: input.slice(length) } } else if (firstByte <= 0xbf) { llength = firstByte - 0xb6 length = safeParseInt(input.slice(1, llength).toString('hex'), 16) data = input.slice(llength, length + llength) if (data.length < length) { throw (new Error('invalid RLP')) } return { data: data, remainder: input.slice(length + llength) } } else if (firstByte <= 0xf7) { // a list between 0-55 bytes long length = firstByte - 0xbf innerRemainder = input.slice(1, length) while (innerRemainder.length) { d = _decode(innerRemainder) decoded.push(d.data) innerRemainder = d.remainder } return { data: decoded, remainder: input.slice(length) } } else { // a list over 55 bytes long llength = firstByte - 0xf6 length = safeParseInt(input.slice(1, llength).toString('hex'), 16) var totalLength = llength + length if (totalLength > input.length) { throw new Error('invalid rlp: total length is larger than the data') } innerRemainder = input.slice(llength, totalLength) if (innerRemainder.length === 0) { throw new Error('invalid rlp, List has a invalid length') } while (innerRemainder.length) { d = _decode(innerRemainder) decoded.push(d.data) innerRemainder = d.remainder } return { data: decoded, remainder: input.slice(totalLength) } } } function isHexPrefixed (str) { return str.slice(0, 2) === '0x' } // Removes 0x from a given String function stripHexPrefix (str) { if (typeof str !== 'string') { return str } return isHexPrefixed(str) ? str.slice(2) : str } function intToHex (i) { var hex = i.toString(16) if (hex.length % 2) { hex = '0' + hex } return hex } function padToEven (a) { if (a.length % 2) a = '0' + a return a } function intToBuffer (i) { var hex = intToHex(i) return Buffer.from(hex, 'hex') } function toBuffer (v) { if (!Buffer.isBuffer(v)) { if (typeof v === 'string') { if (isHexPrefixed(v)) { v = Buffer.from(padToEven(stripHexPrefix(v)), 'hex') } else { v = Buffer.from(v) } } else if (AionLong.isAionLong(v)) { v = AionLong.aionEncodeLong(v) } else if (typeof v === 'number') { if (!v) { v = Buffer.from([]) } else { v = intToBuffer(v) } } else if (v === null || v === undefined) { v = Buffer.from([]) } else if (v.toArray) { // converts a BN to a Buffer v = Buffer.from(v.toArray()) } else { throw new Error('invalid type') } } return v }