diff --git a/src/decoder.js b/src/decoder.js index 98be3c6..70b0940 100644 --- a/src/decoder.js +++ b/src/decoder.js @@ -1,230 +1,225 @@ -'use strict' +"use strict" -const TokenTypes = require('./constants').TokenTypes -const shared = require('./token').OpenPAYGOTokenShared +const TokenTypes = require("./constants").TokenTypes +const shared = require("./token").OpenPAYGOTokenShared class OpenPAYGOTokenDecoder { - static MAX_TOKEN_JUMP = 64 - static MAX_TOKEN_JUMP_COUNTER_SYNC = 100 - static MAX_UNUSED_OLDER_TOKENS = 8 * 2 - - static decodeToken({ - token = '', - secretKeyHex, - count, - usedCounts = undefined, - startingCode = undefined, - valueDivider = 1, - restrictedDigitSet = false, - }) { - if (startingCode === undefined) { - // generate starting code here - startingCode = shared.genStartingCode(secretKeyHex) - } - - let extendedToken = false - - if (!restrictedDigitSet) { - if (token.length <= 9) { - extendedToken = false - } else if (token.length <= 12) { - extendedToken = true - } else { - throw new Error('Token is too long') - } - } else if (restrictedDigitSet) { - if (token.length <= 15) { - extendedToken = false - } else if (token.length <= 20) { - extendedToken = true - } else { - throw new Error('Token is too long') - } - } - - token = Number(token) - - // if (!extendedToken) { - // var { value, tokenType, count, updatedCounts } = - // this.getActivationValueCountAndTypefromToken({ - // token, - // startingCode, - // secretKeyHex, - // count, - // restrictedDigitSet, - // usedCounts, - // }) - // } else { - // var { value, tokenType, count, updatedCounts } = - // this.getActivationValueCountAndTypefromExtendedToken({ - // token, - // startingCode, - // secretKeyHex, - // count, - // restrictedDigitSet, - // usedCounts, - // }) - // } - - var { value, tokenType, newCount, updatedCounts } = - this.getActivationValueCountAndTypefromToken({ - token, - startingCode, - keyHex: secretKeyHex, - count, - restrictedDigitSet, - usedCounts, - }) - - if (value !== undefined && valueDivider) { - value = value / valueDivider - } + static MAX_TOKEN_JUMP = 64 + static MAX_TOKEN_JUMP_COUNTER_SYNC = 100 + static MAX_UNUSED_OLDER_TOKENS = 8 * 2 + + static decodeToken({ + token = "", + secretKeyHex, + count, + usedCounts = undefined, + startingCode = undefined, + valueDivider = 1, + restrictedDigitSet = false, + }) { + if (startingCode === undefined) { + // generate starting code here + startingCode = shared.genStartingCode(secretKeyHex) + } - return { - value: value, - tokenType: tokenType, - count: newCount, - updatedCounts: updatedCounts, - } + let extendedToken = false + + if (!restrictedDigitSet) { + if (token.length <= 9) { + extendedToken = false + } else if (token.length <= 12) { + extendedToken = true + } else { + throw new Error("Token is too long") + } + } else if (restrictedDigitSet) { + if (token.length <= 15) { + extendedToken = false + } else if (token.length <= 20) { + extendedToken = true + } else { + throw new Error("Token is too long") + } } - static getActivationValueCountAndTypefromToken({ + token = Number(token) + + // if (!extendedToken) { + // var { value, tokenType, count, updatedCounts } = + // this.getActivationValueCountAndTypefromToken({ + // token, + // startingCode, + // secretKeyHex, + // count, + // restrictedDigitSet, + // usedCounts, + // }) + // } else { + // var { value, tokenType, count, updatedCounts } = + // this.getActivationValueCountAndTypefromExtendedToken({ + // token, + // startingCode, + // secretKeyHex, + // count, + // restrictedDigitSet, + // usedCounts, + // }) + // } + + var { value, tokenType, newCount, updatedCounts } = + this.getActivationValueCountAndTypefromToken({ token, startingCode, - keyHex, + keyHex: secretKeyHex, count, - restrictedDigitSet = false, - usedCounts = undefined, - }) { - if (restrictedDigitSet) { - token = shared.convertFrom4digitToken(token) - } - let validOldToken = false - // obtain base of token - const tokenBase = shared.getTokenBase(token) - // put base into the starting code - let currentCode = shared.putBaseInToken(startingCode, tokenBase) - // obtain base of starting code - const startCodeBase = shared.getTokenBase(startingCode) - - let value = this.decodeBase(startCodeBase, tokenBase) - let maxCountTry - if (value === TokenTypes.COUNTER_SYNC) { - maxCountTry = count + this.MAX_TOKEN_JUMP_COUNTER_SYNC + 1 - } else { - maxCountTry = count + this.MAX_TOKEN_JUMP + 1 - } + restrictedDigitSet, + usedCounts, + }) - for (let cnt = 0; cnt < maxCountTry; cnt++) { - const maskedToken = shared.putBaseInToken(currentCode, tokenBase) - let tokenType - if (cnt % 2 !== 0) { - if (value === shared.COUNTER_SYNC_VALUE) { - tokenType = TokenTypes.COUNTER_SYNC - } else if (value === shared.PAYG_DISABLE_VALUE) { - tokenType = TokenTypes.PAYG_DISABLE_VALUE - } else { - tokenType = TokenTypes.SET_TIME - } - } else { - tokenType = TokenTypes.ADD_TIME - } - - if (maskedToken === token) { - if ( - this.countIsValid(cnt, count, value, tokenType, usedCounts) - ) { - const updatedCounts = this.updateUsedCounts( - usedCounts, - value, - cnt, - tokenType - ) - return { - value: value, - tokenType: tokenType, - count: cnt, - updatedCounts, - } - } else { - validOldToken = true - } - } - currentCode = shared.genNextToken(currentCode, keyHex) - } - if (validOldToken) { - return { - value: undefined, - tokenType: TokenTypes.ALREADY_USED, - count: undefined, - updatedCounts: undefined, - } - } - return { - value: undefined, - tokenType: TokenTypes.INVALID, - count: undefined, - updatedCounts: undefined, - } + if (value !== undefined && valueDivider) { + value = value / valueDivider } - static countIsValid(count, lastCount, value, type, usedCounts) { - if (value === shared.COUNTER_SYNC_VALUE) { - if (count > lastCount - this.MAX_TOKEN_JUMP) { - return true - } - } else if (count > lastCount) { - return true - } else if (this.MAX_UNUSED_OLDER_TOKENS > 0) { - if (count > lastCount - this.MAX_UNUSED_OLDER_TOKENS) { - if ( - usedCounts.includes(count) && - type === TokenTypes.ADD_TIME - ) { - return True - } - } - } - return false + return { + value: value, + tokenType: tokenType, + count: newCount, + updatedCounts: updatedCounts, + } + } + + static getActivationValueCountAndTypefromToken({ + token, + startingCode, + keyHex, + count, + restrictedDigitSet = false, + usedCounts = undefined, + }) { + if (restrictedDigitSet) { + token = shared.convertFrom4digitToken(token) + } + let validOldToken = false + // obtain base of token + const tokenBase = shared.getTokenBase(token) + // put base into the starting code + let currentCode = shared.putBaseInToken(startingCode, tokenBase) + // obtain base of starting code + const startCodeBase = shared.getTokenBase(startingCode) + + let value = this.decodeBase(startCodeBase, tokenBase) + let maxCountTry + if (value === TokenTypes.COUNTER_SYNC) { + maxCountTry = count + this.MAX_TOKEN_JUMP_COUNTER_SYNC + 1 + } else { + maxCountTry = count + this.MAX_TOKEN_JUMP + 1 } - static updateUsedCounts(pastUsedCounts, value, newCount, type) { - if (pastUsedCounts.length) { - return undefined + for (let cnt = 0; cnt < maxCountTry; cnt++) { + const maskedToken = shared.putBaseInToken(currentCode, tokenBase) + let tokenType + if (cnt % 2 !== 0) { + if (value === shared.COUNTER_SYNC_VALUE) { + tokenType = TokenTypes.COUNTER_SYNC + } else if (value === shared.PAYG_DISABLE_VALUE) { + tokenType = TokenTypes.PAYG_DISABLE_VALUE + } else { + tokenType = TokenTypes.SET_TIME } - let highestCount = pastUsedCounts.length ? Math.max(pastUsedCounts) : 0 - highestCount = newCount > highestCount ? newCount : highestCount - const bottomRange = highestCount - this.MAX_UNUSED_OLDER_TOKENS - const usedCounts = [] - - if ( - type !== TokenTypes.ADD_TIME || - value === shared.COUNTER_SYNC_VALUE || - value === shared.PAYG_DISABLE_VALUE - ) { - // If it is not an Add-Time token, we mark all the past tokens as used in the - // range - for (let cnt = bottomRange; cnt <= highestCount; cnt++) { - usedCounts.push(cnt) - } + } else { + tokenType = TokenTypes.ADD_TIME + } + + if (maskedToken === token) { + if (this.countIsValid(cnt, count, value, tokenType, usedCounts)) { + const updatedCounts = this.updateUsedCounts( + usedCounts, + value, + cnt, + tokenType + ) + return { + value: value, + tokenType: tokenType, + count: cnt, + updatedCounts, + } } else { - // If it is an Add-Time token, we just mark the tokens actually used in the - // range - for (let cnt = bottomRange; cnt <= highestCount; cnt++) { - if (cnt === newCount || pastUsedCounts.includes(cnt)) { - usedCounts.push(cnt) - } - } + validOldToken = true + } + } + currentCode = shared.genNextToken(currentCode, keyHex) + } + if (validOldToken) { + return { + value: undefined, + tokenType: TokenTypes.ALREADY_USED, + count: undefined, + updatedCounts: undefined, + } + } + return { + value: undefined, + tokenType: TokenTypes.INVALID, + count: undefined, + updatedCounts: undefined, + } + } + + static countIsValid(count, lastCount, value, type, usedCounts) { + if (value === shared.COUNTER_SYNC_VALUE) { + if (count > lastCount - this.MAX_TOKEN_JUMP) { + return true + } + } else if (count > lastCount) { + return true + } else if (this.MAX_UNUSED_OLDER_TOKENS > 0) { + if (count > lastCount - this.MAX_UNUSED_OLDER_TOKENS) { + if (usedCounts.includes(count) && type === TokenTypes.ADD_TIME) { + return True } - return usedCounts + } } + return false + } - static decodeBase(startCodeBase, tokenBase) { - const decodedValue = tokenBase - startCodeBase - return decodedValue < 0 ? decodedValue + 1000 : decodedValue + static updateUsedCounts(pastUsedCounts, value, newCount, type) { + if (pastUsedCounts.length) { + return undefined + } + let highestCount = pastUsedCounts.length ? Math.max(pastUsedCounts) : 0 + highestCount = newCount > highestCount ? newCount : highestCount + const bottomRange = highestCount - this.MAX_UNUSED_OLDER_TOKENS + const usedCounts = [] + + if ( + type !== TokenTypes.ADD_TIME || + value === shared.COUNTER_SYNC_VALUE || + value === shared.PAYG_DISABLE_VALUE + ) { + // If it is not an Add-Time token, we mark all the past tokens as used in the + // range + for (let cnt = bottomRange; cnt <= highestCount; cnt++) { + usedCounts.push(cnt) + } + } else { + // If it is an Add-Time token, we just mark the tokens actually used in the + // range + for (let cnt = bottomRange; cnt <= highestCount; cnt++) { + if (cnt === newCount || pastUsedCounts.includes(cnt)) { + usedCounts.push(cnt) + } + } } + return usedCounts + } + + static decodeBase(startCodeBase, tokenBase) { + const decodedValue = tokenBase - startCodeBase + return decodedValue < 0 ? decodedValue + 1000 : decodedValue + } } module.exports = { - OpenPAYGOTokenDecoder: OpenPAYGOTokenDecoder, + OpenPAYGOTokenDecoder: OpenPAYGOTokenDecoder, } diff --git a/src/encoder.js b/src/encoder.js index 1951e9c..5c764f8 100644 --- a/src/encoder.js +++ b/src/encoder.js @@ -1,170 +1,164 @@ -'use strict' +"use strict" -const TokenTypes = require('./constants').TokenTypes -const shared = require('./token').OpenPAYGOTokenShared -const sharedExtended = require('./token').OpenPAYGOTokenSharedExtended +const TokenTypes = require("./constants").TokenTypes +const shared = require("./token").OpenPAYGOTokenShared +const sharedExtended = require("./token").OpenPAYGOTokenSharedExtended class OpenPAYGOTokenEncoder { - constructor() {} + constructor() {} - generateToken({ - secretKeyHex, - count, - value = undefined, - tokenType = TokenTypes.SET_TIME, - startingCode = undefined, - valueDivider = 1, - restrictDigitSet = false, - extendToken = false, - }) { - if (startingCode === undefined) { - // generate starting code here - startingCode = shared.genStartingCode(secretKeyHex) - } + generateToken({ + secretKeyHex, + count, + value = undefined, + tokenType = TokenTypes.SET_TIME, + startingCode = undefined, + valueDivider = 1, + restrictDigitSet = false, + extendToken = false, + }) { + if (startingCode === undefined) { + // generate starting code here + startingCode = shared.genStartingCode(secretKeyHex) + } - if ( - tokenType === TokenTypes.ADD_TIME || - tokenType === TokenTypes.SET_TIME - ) { - if (value === undefined) { - throw new Error("'value' argument is undefined.") - } - value = Math.round(value * valueDivider) - 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) { - throw new Error('A value is not allowed for this token type.') - } else { - if (tokenType === TokenTypes.DISABLE_PAYG) { - value = shared.PAYG_DISABLE_VALUE - } else if (tokenType === TokenTypes.COUNTER_SYNC) { - value = shared.COUNTER_SYNC_VALUE - } else { - throw new Error('The token type provided is not supported.') - } - } + if ( + tokenType === TokenTypes.ADD_TIME || + tokenType === TokenTypes.SET_TIME + ) { + if (value === undefined) { + throw new Error("'value' argument is undefined.") + } + value = Math.round(value * valueDivider) + 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) { + throw new Error("A value is not allowed for this token type.") + } else { + if (tokenType === TokenTypes.DISABLE_PAYG) { + value = shared.PAYG_DISABLE_VALUE + } else if (tokenType === TokenTypes.COUNTER_SYNC) { + value = shared.COUNTER_SYNC_VALUE + } else { + throw new Error("The token type provided is not supported.") + } + } - if (extendToken) { - return this.generateExtendedToken({ - startingCode: startingCode, - key: secretKeyHex, - count: count, - value: value, - mode: tokenType, - restrictDigitSet: restrictDigitSet, - }) - } - return this.generateStandardToken({ - startingCode: startingCode, - key: secretKeyHex, - count: count, - value: value, - mode: tokenType, - restrictDigitSet: restrictDigitSet, - }) + if (extendToken) { + return this.generateExtendedToken({ + startingCode: startingCode, + key: secretKeyHex, + count: count, + value: value, + mode: tokenType, + restrictDigitSet: restrictDigitSet, + }) } + return this.generateStandardToken({ + startingCode: startingCode, + key: secretKeyHex, + count: count, + value: value, + mode: tokenType, + restrictDigitSet: restrictDigitSet, + }) + } - generateStandardToken({ - startingCode = undefined, - key = undefined, - value = undefined, - count = undefined, - mode = TokenTypes.ADD_TIME, - restrictDigitSet = false, - }) { - const startingBaseCode = shared.getTokenBase(startingCode) - const tokenBase = this.encodeBase(startingBaseCode, value) - let currentToken = shared.putBaseInToken(startingCode, tokenBase) - const newCount = this.getNewCount(count, mode) + generateStandardToken({ + startingCode = undefined, + key = undefined, + value = undefined, + count = undefined, + mode = TokenTypes.ADD_TIME, + restrictDigitSet = false, + }) { + const startingBaseCode = shared.getTokenBase(startingCode) + const tokenBase = this.encodeBase(startingBaseCode, value) + let currentToken = shared.putBaseInToken(startingCode, tokenBase) + const newCount = this.getNewCount(count, mode) - for (let i = 0; i < newCount - 1; i++) { - currentToken = shared.genNextToken(currentToken, key) - } - let finalToken = shared.putBaseInToken(currentToken, tokenBase) + for (let i = 0; i < newCount - 1; i++) { + currentToken = shared.genNextToken(currentToken, key) + } + let finalToken = shared.putBaseInToken(currentToken, tokenBase) - if (restrictDigitSet) { - finalToken = shared.convertToNdigitToken( - finalToken, - 15 - ) - finalToken = String(finalToken).padStart(15, '0') - } else { - finalToken = String(finalToken).padStart(9, '0') - } - return { - newCount, - finalToken, - } + if (restrictDigitSet) { + finalToken = shared.convertToNdigitToken(finalToken, 15) + finalToken = String(finalToken).padStart(15, "0") + } else { + finalToken = String(finalToken).padStart(9, "0") } + return { + newCount, + finalToken, + } + } - 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) + 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) + 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, - } + 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 - } - return value + baseCode + encodeBase(baseCode, value) { + if (value + baseCode > 999) { + return value + baseCode - 1000 } + return value + baseCode + } - getNewCount(count, mode) { - let newCount - const currCountOdd = count % 2 - if ( - mode === TokenTypes.SET_TIME || - mode === TokenTypes.DISABLE_PAYG || - mode === TokenTypes.COUNTER_SYNC - ) { - if (currCountOdd) { - newCount = count + 2 - } else { - newCount = count + 1 - } - } else { - if (currCountOdd) { - newCount = count + 1 - } else { - newCount = count + 2 - } - } - return newCount + getNewCount(count, mode) { + let newCount + const currCountOdd = count % 2 + if ( + mode === TokenTypes.SET_TIME || + mode === TokenTypes.DISABLE_PAYG || + mode === TokenTypes.COUNTER_SYNC + ) { + if (currCountOdd) { + newCount = count + 2 + } else { + newCount = count + 1 + } + } else { + if (currCountOdd) { + newCount = count + 1 + } else { + newCount = count + 2 + } } + return newCount + } } module.exports = { - OpenPAYGOTokenEncoder, + OpenPAYGOTokenEncoder, } diff --git a/src/token.js b/src/token.js index 3644fc5..ec94e20 100644 --- a/src/token.js +++ b/src/token.js @@ -1,234 +1,234 @@ -'use strict' +"use strict" -const bigintConversion = require('bigint-conversion') -const siphash24 = require('siphash') -const Buffer = require('buffer/').Buffer +const bigintConversion = require("bigint-conversion") +const siphash24 = require("siphash") +const Buffer = require("buffer/").Buffer class OpenPAYGOTokenShared { - // token configuration - static MAX_BASE = 999 - static MAX_ACTIVATION_VALUE = 995 - static PAYG_DISABLE_VALUE = 998 - static COUNTER_SYNC_VALUE = 999 - static TOKEN_VALUE_OFFSET = 1000 - - constructor() {} - - static getTokenBase(code) { - return code % this.TOKEN_VALUE_OFFSET - } - - static putBaseInToken(token, tokenBase) { - if (tokenBase > this.MAX_BASE) { - throw new Error('Invalid token base value') - } - 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 << (32 - 2 + 1)) - 1) << 2 - let temp = (hash_uint & mask) >>> 2 - if (temp > 999999999) temp = temp - 73741825 - return temp - } - - static convertFrom4digitToken(token) { - return convertFrom4digitToken(token) - } - - static genStartingCode(key) { - const hash = this.genHash({ key: key, msg: key }) - return this.convertHash2Token(hash) - } - - 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 - } + // token configuration + static MAX_BASE = 999 + static MAX_ACTIVATION_VALUE = 995 + static PAYG_DISABLE_VALUE = 998 + static COUNTER_SYNC_VALUE = 999 + static TOKEN_VALUE_OFFSET = 1000 + + constructor() {} + + static getTokenBase(code) { + return code % this.TOKEN_VALUE_OFFSET + } + + static putBaseInToken(token, tokenBase) { + if (tokenBase > this.MAX_BASE) { + throw new Error("Invalid token base value") + } + 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 << (32 - 2 + 1)) - 1) << 2 + let temp = (hash_uint & mask) >>> 2 + if (temp > 999999999) temp = temp - 73741825 + return temp + } + + static convertFrom4digitToken(token) { + return convertFrom4digitToken(token) + } + + static genStartingCode(key) { + const hash = this.genHash({ key: key, msg: key }) + return this.convertHash2Token(hash) + } + + 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 - - 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 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 }) { - 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 - } + static MAX_BASE_EXTENDED = 999999 + static MAX_ACTIVATION_VALUE_EXTENDED = 999999 + static TOKEN_VALUE_OFFSET_EXTENDED = 1000000 + + 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 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 }) { + 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 + } } function bitArrayFromInt(number, bitLength) { - let bitArray = [] - for (let i = 0; i < bitLength; i++) { - bitArray.push((number & (1 << (bitLength - 1 - i))) !== 0) - } - return bitArray + let bitArray = [] + for (let i = 0; i < bitLength; i++) { + bitArray.push((number & (1 << (bitLength - 1 - i))) !== 0) + } + return bitArray } function bitArrayToInt(bit_array) { - let num = 0 - for (let bit of bit_array) { - num = (num << 1) | bit - } - return num + let num = 0 + for (let bit of bit_array) { + num = (num << 1) | bit + } + 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) + // 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) + // 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, + OpenPAYGOTokenShared: OpenPAYGOTokenShared, + OpenPAYGOTokenSharedExtended: OpenPAYGOTokenSharedExtended, } diff --git a/test/decoder.test.js b/test/decoder.test.js index 4dd363a..a307e2d 100644 --- a/test/decoder.test.js +++ b/test/decoder.test.js @@ -1,25 +1,23 @@ -const sample = require('./sample_tokens.json') -const TokenTypes = require('../src/constants').TokenTypes -const Decorder = require('../src/decoder').OpenPAYGOTokenDecoder +const sample = require("./sample_tokens.json") +const TokenTypes = require("../src/constants").TokenTypes +const Decorder = require("../src/decoder").OpenPAYGOTokenDecoder -describe('OpenPAYGOTokenDecoder test', () => { - test('decodeToken', () => { - const data = sample[0] +describe("OpenPAYGOTokenDecoder test", () => { + test("decodeToken", () => { + const data = sample[0] - const { value, tokenType, count, updatedCounts } = Decorder.decodeToken( - { - token: data.token, - secretKeyHex: data.key, - count: data.token_count, - usedCounts: [], - startingCode: data.starting_code, - restrictedDigitSet: data.restricted_digit_set, - } - ) - - expect(value).toBeUndefined() - expect(count).toBeUndefined() - expect(updatedCounts).toBeUndefined() - expect(tokenType).toEqual(TokenTypes.ALREADY_USED) + const { value, tokenType, count, updatedCounts } = Decorder.decodeToken({ + token: data.token, + secretKeyHex: data.key, + count: data.token_count, + usedCounts: [], + startingCode: data.starting_code, + restrictedDigitSet: data.restricted_digit_set, }) + + expect(value).toBeUndefined() + expect(count).toBeUndefined() + expect(updatedCounts).toBeUndefined() + expect(tokenType).toEqual(TokenTypes.ALREADY_USED) + }) }) diff --git a/test/encoder.test.js b/test/encoder.test.js index 4eb558f..33018f3 100644 --- a/test/encoder.test.js +++ b/test/encoder.test.js @@ -1,29 +1,29 @@ -const sample = require('./sample_tokens.json') -const Encoder = require('../src/encoder').OpenPAYGOTokenEncoder +const sample = require("./sample_tokens.json") +const Encoder = require("../src/encoder").OpenPAYGOTokenEncoder -describe('OpenPAYGOTokenEncoder test', () => { - test('generateToken', () => { - const encoder = new Encoder() +describe("OpenPAYGOTokenEncoder test", () => { + test("generateToken", () => { + const encoder = new Encoder() - sample.forEach((s) => { - const data = s - try { - const { finalToken } = encoder.generateToken({ - tokenType: data.token_type, - secretKeyHex: data.key, - count: data.token_count, - startingCode: data.starting_code, - restrictDigitSet: data.restricted_digit_set, - value: data.value_raw, - extendToken: false, - }) - - expect(finalToken).toBe(data.token) - } catch (err) { - if (!err.message.includes('The value provided is too high.')) { - throw err - } - } + sample.forEach((s) => { + const data = s + try { + const { finalToken } = encoder.generateToken({ + tokenType: data.token_type, + secretKeyHex: data.key, + count: data.token_count, + startingCode: data.starting_code, + restrictDigitSet: data.restricted_digit_set, + value: data.value_raw, + extendToken: false, }) + + expect(finalToken).toBe(data.token) + } catch (err) { + if (!err.message.includes("The value provided is too high.")) { + throw err + } + } }) + }) }) diff --git a/test/token.test.js b/test/token.test.js index 775f366..ea6fafa 100644 --- a/test/token.test.js +++ b/test/token.test.js @@ -1,36 +1,36 @@ -const tokenLib = require('../src/token').OpenPAYGOTokenShared -const bigintConversion = require('bigint-conversion') +const tokenLib = require("../src/token").OpenPAYGOTokenShared +const bigintConversion = require("bigint-conversion") -describe('OpenPAYGOTokenShared test', () => { - test('OpenPAYGOTokenShared genHash', () => { - const hash = tokenLib.genHash({ - key: Buffer.from('0123456789ABCDEF'), - msg: '', - }) - - expect(bigintConversion.hexToBigint(hash)).toBe(3627314469837380007n) +describe("OpenPAYGOTokenShared test", () => { + test("OpenPAYGOTokenShared genHash", () => { + const hash = tokenLib.genHash({ + key: Buffer.from("0123456789ABCDEF"), + msg: "", }) - test('OpenPAYGOTokenShared convertHash2Token', () => { - const hash = tokenLib.genHash({ - key: 'bc41ec9530f6dac86b1a29ab82edc5fb', - msg: 'hello world', - }) - const token = tokenLib.convertHash2Token(hash) - expect(token).toBe(184900559) - }) + expect(bigintConversion.hexToBigint(hash)).toBe(3627314469837380007n) + }) - test('OpenPAYGOTokenShared genNextToken', () => { - const startingCode = 516959010 - const newToken = tokenLib.genNextToken( - startingCode, - 'bc41ec9530f6dac86b1a29ab82edc5fb' - ) - expect(newToken).toBe(117642353) + test("OpenPAYGOTokenShared convertHash2Token", () => { + const hash = tokenLib.genHash({ + key: "bc41ec9530f6dac86b1a29ab82edc5fb", + msg: "hello world", }) + const token = tokenLib.convertHash2Token(hash) + expect(token).toBe(184900559) + }) - test('OpenPAYGOTokenShared convertToNdigitToken', () => { - const token = 854849256 - expect(tokenLib.convertToNdigitToken(token, 15)).toBe(413441444234331) - }) + test("OpenPAYGOTokenShared genNextToken", () => { + const startingCode = 516959010 + const newToken = tokenLib.genNextToken( + startingCode, + "bc41ec9530f6dac86b1a29ab82edc5fb" + ) + expect(newToken).toBe(117642353) + }) + + test("OpenPAYGOTokenShared convertToNdigitToken", () => { + const token = 854849256 + expect(tokenLib.convertToNdigitToken(token, 15)).toBe(413441444234331) + }) })