Skip to content
Open
27 changes: 15 additions & 12 deletions common/src/utils/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,30 @@ export function calculateUserIdentifierHash(
return BigInt(ripemdHash);
}

export function customHasher(pubKeyFormatted: string[]) {
if (pubKeyFormatted.length < 16) {
export function customHasher(pubKeyFormatted: Array<string | bigint | number>) {
const asBigints: bigint[] = pubKeyFormatted.map((v) =>
typeof v === 'bigint' ? v : BigInt(v)
);
Comment on lines +44 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add input validation and error handling for BigInt conversion.

The BigInt() conversion can throw TypeError for invalid inputs (non-numeric strings, undefined, null, etc.). Since customHasher is a public exported utility, unvalidated inputs could cause runtime crashes without helpful error messages.

Consider adding validation:

 export function customHasher(pubKeyFormatted: Array<string | bigint | number>) {
+  if (!Array.isArray(pubKeyFormatted) || pubKeyFormatted.length === 0) {
+    throw new Error('customHasher requires a non-empty array');
+  }
+  
+  try {
     const asBigints: bigint[] = pubKeyFormatted.map((v) =>
       typeof v === 'bigint' ? v : BigInt(v)
     );
+  } catch (error) {
+    throw new Error(`Invalid input for customHasher: ${error instanceof Error ? error.message : 'Cannot convert to BigInt'}`);
+  }
🤖 Prompt for AI Agents
In common/src/utils/hash.ts around lines 44 to 47, the direct BigInt conversion
can throw for invalid inputs; add input validation and error handling before
mapping: first verify pubKeyFormatted is an array and not null/undefined, then
for each element explicitly check for null/undefined and for allowed types
(bigint, number, numeric-string) and reject or coerce others; wrap the BigInt
conversion in a try/catch (or validate numeric-string with a regexp) and throw a
clear, descriptive TypeError that includes the offending value and its index so
callers get actionable errors instead of uncaught exceptions.

if (asBigints.length < 16) {
// if k is less than 16, we can use a single poseidon hash
return flexiblePoseidon(pubKeyFormatted.map(BigInt)).toString();
return flexiblePoseidon(asBigints).toString();
} else {
const rounds = Math.ceil(pubKeyFormatted.length / 16); // do up to 16 rounds of poseidon
const rounds = Math.ceil(asBigints.length / 16); // do up to 16 rounds of poseidon
if (rounds > 16) {
throw new Error('Number of rounds is greater than 16');
}
const hash = new Array(rounds);
for (let i = 0; i < rounds; i++) {
hash[i] = { inputs: new Array(16).fill(BigInt(0)) };
}
const blocks: bigint[][] = Array.from({ length: rounds }, () =>
new Array<bigint>(16).fill(0n)
);
for (let i = 0; i < rounds; i++) {
for (let j = 0; j < 16; j++) {
if (i * 16 + j < pubKeyFormatted.length) {
hash[i].inputs[j] = BigInt(pubKeyFormatted[i * 16 + j]);
const idx = i * 16 + j;
if (idx < asBigints.length) {
blocks[i][j] = asBigints[idx];
}
}
}
const finalHash = flexiblePoseidon(hash.map((h) => poseidon16(h.inputs)));
const finalHash = flexiblePoseidon(blocks.map((b) => poseidon16(b)));
return finalHash.toString();
}
}
Expand Down Expand Up @@ -184,5 +187,5 @@ export function hash(

export function packBytesAndPoseidon(unpacked: number[]) {
const packed = packBytesArray(unpacked);
return customHasher(packed.map(String)).toString();
return customHasher(packed);
}
Loading