diff --git a/src/helpers/common.ts b/src/helpers/common.ts index 54d1988..05be00b 100644 --- a/src/helpers/common.ts +++ b/src/helpers/common.ts @@ -104,3 +104,23 @@ export function encParamsHexToBuf(eciesData: Omit): Omit mac: Buffer.from(eciesData.mac, "hex"), }; } + +export function calculateMedian(arr: number[]): number { + const arrSize = arr.length; + + if (arrSize === 0) return 0; + const sortedArr = arr.sort(function (a, b) { + return a - b; + }); + + // odd length + if (arrSize % 2 !== 0) { + return sortedArr[Math.floor(arrSize / 2)]; + } + + // return average of two mid values in case of even arrSize + const mid1 = sortedArr[arrSize / 2 - 1]; + + const mid2 = sortedArr[arrSize / 2]; + return (mid1 + mid2) / 2; +} diff --git a/src/helpers/nodeUtils.ts b/src/helpers/nodeUtils.ts index 598c3d2..cc32c4e 100644 --- a/src/helpers/nodeUtils.ts +++ b/src/helpers/nodeUtils.ts @@ -27,7 +27,7 @@ import { } from "../interfaces"; import log from "../loglevel"; import { Some } from "../some"; -import { kCombinations, normalizeKeysResult, normalizeLegacyKeysResult, thresholdSame } from "./common"; +import { calculateMedian, kCombinations, normalizeKeysResult, normalizeLegacyKeysResult, thresholdSame } from "./common"; import { generateAddressFromPrivKey, generateAddressFromPubKey, keccak256 } from "./keyUtils"; import { lagrangeInterpolation } from "./langrangeInterpolatePoly"; import { decryptNodeData, getMetadata, getOrSetNonce } from "./metadataUtils"; @@ -104,16 +104,16 @@ export const GetPubKeyOrKeyAssign = async (params: { // push only those indexes for nodes who are returning pub key matching with threshold pub key. // this check is important when different nodes have different keys assigned to a user. if (currentNodePubKey === thresholdPubKey) { - const nodeIndex = parseInt(x1.result.node_index); + const nodeIndex = Number.parseInt(x1.result.node_index); if (nodeIndex) nodeIndexes.push(nodeIndex); } - const serverTimeOffset = x1.result.server_time_offset ? parseInt(x1.result.server_time_offset, 10) : 0; + const serverTimeOffset = x1.result.server_time_offset ? Number.parseInt(x1.result.server_time_offset, 10) : 0; serverTimeOffsets.push(serverTimeOffset); } }); } - const serverTimeOffset = Math.max(...serverTimeOffsets); + const serverTimeOffset = keyResult ? calculateMedian(serverTimeOffsets) : 0; return Promise.resolve({ keyResult, serverTimeOffset, nodeIndexes, errorResult, nonceResult }); } return Promise.reject( @@ -532,14 +532,14 @@ export async function retrieveOrImportShare(params: { const thresholdIsNewKey = thresholdSame(isNewKeyResponses, ~~(endpoints.length / 2) + 1); // Convert each string timestamp to a number - const serverOffsetTimes = serverTimeOffsetResponses.map((timestamp) => parseInt(timestamp, 10)); + const serverOffsetTimes = serverTimeOffsetResponses.map((timestamp) => Number.parseInt(timestamp, 10)); return { privateKey, sessionTokenData, thresholdNonceData, nodeIndexes, isNewKey: thresholdIsNewKey === "true", - serverTimeOffsetResponse: serverTimeOffset || Math.max(...serverOffsetTimes), + serverTimeOffsetResponse: serverTimeOffset || calculateMedian(serverOffsetTimes), }; } throw new Error("Invalid"); @@ -685,14 +685,14 @@ export const legacyKeyLookup = async (endpoints: string[], verifier: string, ver lookupResults.forEach((x1) => { if (x1 && x1.result) { const timeOffSet = x1.result.server_time_offset; - const serverTimeOffset = timeOffSet ? parseInt(timeOffSet, 10) : 0; + const serverTimeOffset = timeOffSet ? Number.parseInt(timeOffSet, 10) : 0; serverTimeOffsets.push(serverTimeOffset); } }); } - const serverTimeOffset = Math.max(...serverTimeOffsets); if (keyResult || errorResult) { + const serverTimeOffset = keyResult ? calculateMedian(serverTimeOffsets) : 0; return Promise.resolve({ keyResult, errorResult, serverTimeOffset }); } return Promise.reject(new Error(`invalid results ${JSON.stringify(lookupResults)}`)); diff --git a/src/torus.ts b/src/torus.ts index 651aef2..9d1c91d 100644 --- a/src/torus.ts +++ b/src/torus.ts @@ -15,6 +15,7 @@ import stringify from "json-stable-stringify"; import { config } from "./config"; import { + calculateMedian, encParamsBufToHex, generateAddressFromPrivKey, generateAddressFromPubKey, @@ -404,7 +405,7 @@ class Torus { for (let i = 0; i < shareResponses.length; i += 1) { const currentShareResponse = shareResponses[i] as JRPCResponse; const timeOffSet = currentShareResponse?.result?.server_time_offset; - const parsedTimeOffset = timeOffSet ? parseInt(timeOffSet, 10) : 0; + const parsedTimeOffset = timeOffSet ? Number.parseInt(timeOffSet, 10) : 0; serverTimeOffsets.push(parsedTimeOffset); if (currentShareResponse?.result?.keys?.length > 0) { currentShareResponse.result.keys.sort((a, b) => new BN(a.Index, 16).cmp(new BN(b.Index, 16))); @@ -464,7 +465,7 @@ class Torus { if (privateKey === undefined || privateKey === null) { throw new Error("could not derive private key"); } - return { privateKey, serverTimeOffset: this.serverTimeOffset || Math.max(...serverTimeOffsets) }; + return { privateKey, serverTimeOffset: this.serverTimeOffset || calculateMedian(serverTimeOffsets) }; } throw new Error("invalid"); });