diff --git a/src/encoder.js b/src/encoder.js index 5b0a995..1951e9c 100644 --- a/src/encoder.js +++ b/src/encoder.js @@ -2,6 +2,7 @@ const TokenTypes = require('./constants').TokenTypes const shared = require('./token').OpenPAYGOTokenShared +const sharedExtended = require('./token').OpenPAYGOTokenSharedExtended class OpenPAYGOTokenEncoder { constructor() {} @@ -29,7 +30,10 @@ class OpenPAYGOTokenEncoder { throw new Error("'value' argument is undefined.") } value = Math.round(value * valueDivider) - if (value > shared.MAX_ACTIVATION_VALUE) { + const maxValue = extendToken + ? sharedExtended.MAX_ACTIVATION_VALUE_EXTENDED + : shared.MAX_ACTIVATION_VALUE + if (value > maxValue) { throw new Error('The value provided is too high.') } } else if (value !== undefined) { @@ -44,6 +48,16 @@ class OpenPAYGOTokenEncoder { } } + if (extendToken) { + return this.generateExtendedToken({ + startingCode: startingCode, + key: secretKeyHex, + count: count, + value: value, + mode: tokenType, + restrictDigitSet: restrictDigitSet, + }) + } return this.generateStandardToken({ startingCode: startingCode, key: secretKeyHex, @@ -73,7 +87,10 @@ class OpenPAYGOTokenEncoder { let finalToken = shared.putBaseInToken(currentToken, tokenBase) if (restrictDigitSet) { - finalToken = shared.convertToNdigitToken(finalToken) + finalToken = shared.convertToNdigitToken( + finalToken, + 15 + ) finalToken = String(finalToken).padStart(15, '0') } else { finalToken = String(finalToken).padStart(9, '0') @@ -84,6 +101,39 @@ class OpenPAYGOTokenEncoder { } } + generateExtendedToken({ + startingCode = undefined, + key = undefined, + value = undefined, + count = undefined, + mode = TokenTypes.ADD_TIME, + restrictDigitSet = false, + }) { + const startingBaseCode = sharedExtended.getTokenBase(startingCode) + const tokenBase = this.encodeBase(startingBaseCode, value) + let currentToken = sharedExtended.putBaseInToken( + startingCode, + tokenBase + ) + const newCount = this.getNewCount(count, mode) + + for (let i = 0; i < newCount - 1; i++) { + currentToken = sharedExtended.genNextToken(currentToken, key) + } + let finalToken = sharedExtended.putBaseInToken(currentToken, tokenBase) + + if (restrictDigitSet) { + finalToken = sharedExtended.convertToNdigitToken(finalToken) + finalToken = String(finalToken).padStart(20, '0') + } else { + finalToken = String(finalToken).padStart(12, '0') + } + return { + newCount, + finalToken, + } + } + encodeBase(baseCode, value) { if (value + baseCode > 999) { return value + baseCode - 1000 diff --git a/src/token.js b/src/token.js index 5a299c7..3644fc5 100644 --- a/src/token.js +++ b/src/token.js @@ -70,14 +70,7 @@ class OpenPAYGOTokenShared { } static convertFrom4digitToken(token) { - // 4 - digit token value - let bitArray = [] - for (let digit of token.toString()) { - digit = Number(digit) - 1 - const tempArr = bitArrayFromInt(digit, 2) - bitArray = bitArray.concat(tempArr) - } - return bitArrayToInt(bitArray) + return convertFrom4digitToken(token) } static genStartingCode(key) { @@ -85,17 +78,96 @@ class OpenPAYGOTokenShared { return this.convertHash2Token(hash) } - static convertToNdigitToken(token, digit = 4) { - // token should be a number data type - let restrictedDigitToken = '' - let bitArray = bitArrayFromInt(token, digit * 2) + static convertToNdigitToken(token, digit = 15) { + return convertToNdigitToken(token, digit) + } + + static genHash({ key, msg }) { + let buf + if (typeof key === 'object') { + buf = key + } else { + buf = bigintConversion.hexToBuf(key) + } + const arrayBuffer = buf.buffer.slice( + buf.byteOffset, + buf.byteOffset + buf.byteLength + ) + const uint32Array = new Uint32Array(arrayBuffer) + const hash = siphash24.hash_hex(uint32Array, msg) + + return hash + } +} + +class OpenPAYGOTokenSharedExtended { + static MAX_BASE_EXTENDED = 999999 + static MAX_ACTIVATION_VALUE_EXTENDED = 999999 + static TOKEN_VALUE_OFFSET_EXTENDED = 1000000 - for (let i = 0; i < digit; i++) { - restrictedDigitToken += bitArrayToInt( - bitArray.slice(i * 2, i * 2 + 2) - ).toString() + constructor() {} + + static getTokenBase(code) { + return code % this.TOKEN_VALUE_OFFSET_EXTENDED + } + + static putBaseInToken(token, tokenBase) { + if (tokenBase > this.MAX_BASE_EXTENDED) { + throw new Error('Invalid token base value') } - return Number(restrictedDigitToken) + return token - this.getTokenBase(token) + tokenBase + } + + static genNextToken(prev_token, key) { + const conformedToken = Buffer.alloc(4) // Allocate buffer of 4 bytes + conformedToken.writeUInt32BE(prev_token, 0) // Write the integer value into buffer as big-endian + + // Duplicate the buffer by concatenating it with itself + const duplicatedToken = Buffer.concat([conformedToken, conformedToken]) + let hash = this.genHash({ + key: key, + msg: duplicatedToken, + asByte: true, + }) + + return this.convertHash2Token(hash) + } + + static convertHash2Token(hash) { + // convert hash from hex + + const hashBuffer = bigintConversion.hexToBuf(hash) + + const dView = new DataView( + hashBuffer.buffer, + hashBuffer.byteOffset, + hashBuffer.byteLength + ) + + // take first 4 btypes + const hash_msb = dView.getUint32(0) // as big endian + // take last 4 bytes + const hash_lsb = dView.getUint32(4) // as big endian + + const hashUnit = (hash_msb ^ hash_lsb) >>> 0 + const token = this.convertTo29_5_bits(hashUnit) + return token + } + + static convertTo29_5_bits(hash_uint) { + // port from original lib + const mask = ((1 << (64 - 24 + 1)) - 1) << 24 + let temp = (hash_uint & mask) >>> 24 + if (temp > 999999999999) temp = temp - 99511627777 + return temp + } + + static convertFrom4digitToken(token) { + return convertFrom4digitToken(token) + } + + static convertToNdigitToken(token, digit = 20) { + return convertToNdigitToken(token, digit) } static genHash({ key, msg }) { @@ -119,7 +191,7 @@ class OpenPAYGOTokenShared { function bitArrayFromInt(number, bitLength) { let bitArray = [] for (let i = 0; i < bitLength; i++) { - bitArray.push(number & (1 << (bitLength - 1 - i))) + bitArray.push((number & (1 << (bitLength - 1 - i))) !== 0) } return bitArray } @@ -132,6 +204,31 @@ function bitArrayToInt(bit_array) { return num } +function convertFrom4digitToken(token) { + // 4 - digit token value + let bitArray = [] + for (let digit of token.toString()) { + digit = Number(digit) - 1 + const tempArr = bitArrayFromInt(digit, 2) + bitArray = bitArray.concat(tempArr) + } + return bitArrayToInt(bitArray) +} + +function convertToNdigitToken(token, digit = 4) { + // token should be a number data type + let restrictedDigitToken = '' + let bitArray = bitArrayFromInt(token, digit * 2) + + for (let i = 0; i < digit; i++) { + restrictedDigitToken += ( + bitArrayToInt(bitArray.slice(i * 2, i * 2 + 2)) + 1 + ).toString() + } + return parseInt(restrictedDigitToken, 10) +} + module.exports = { OpenPAYGOTokenShared: OpenPAYGOTokenShared, + OpenPAYGOTokenSharedExtended: OpenPAYGOTokenSharedExtended, } diff --git a/test/encoder.test.js b/test/encoder.test.js index 2c31910..4eb558f 100644 --- a/test/encoder.test.js +++ b/test/encoder.test.js @@ -7,7 +7,6 @@ describe('OpenPAYGOTokenEncoder test', () => { sample.forEach((s) => { const data = s - try { const { finalToken } = encoder.generateToken({ tokenType: data.token_type, @@ -16,6 +15,7 @@ describe('OpenPAYGOTokenEncoder test', () => { startingCode: data.starting_code, restrictDigitSet: data.restricted_digit_set, value: data.value_raw, + extendToken: false, }) expect(finalToken).toBe(data.token) diff --git a/test/token.test.js b/test/token.test.js index 3b6fb6d..775f366 100644 --- a/test/token.test.js +++ b/test/token.test.js @@ -28,4 +28,9 @@ describe('OpenPAYGOTokenShared test', () => { ) expect(newToken).toBe(117642353) }) + + test('OpenPAYGOTokenShared convertToNdigitToken', () => { + const token = 854849256 + expect(tokenLib.convertToNdigitToken(token, 15)).toBe(413441444234331) + }) })