Skip to content

fix(mock-contracts): give each input of a mocks batch its own ctHash - #482

Merged
alexshchur merged 1 commit into
FhenixProtocol:masterfrom
erkancamli:fix/mock-batch-distinct-cthash
Nov 2, 2026
Merged

alexshchur merged 1 commit into
FhenixProtocol:masterfrom
erkancamli:fix/mock-batch-distinct-cthash

Conversation

@erkancamli

@erkancamli erkancamli commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On Hardhat, encryptInputs returns the same ctHash for equal values encrypted in one batch:

const [a, b, signature] = await client
  .encryptInputs([Encryptable.uint32(9n), Encryptable.uint32(9n)])
  .setConsumingContract(contract)
  .execute();
// a === b

So two separate inputs become one handle in the mocks. A contract that keys anything on the handle (a mapping, a per handle FHE.allow, a check that an input is not used twice) sees one value where it was given two. Each encryption against CoFHE is a fresh ciphertext, and the Foundry mocks already give every input its own hash.

Cause

The SDK mocks path calculates all ctHashes of a batch with one view call to MockZkVerifier.zkVerifyCalcCtHashesPacked, then stores them with insertPackedCtHashes. The hash commits to the value, the account and salt, but salt only advances in insertCtHash, so every item of the batch is calculated with the same salt. Equal values (with the same utype) then hash to the same ctHash.

The Foundry CofheClient does not have this problem because it calls zkVerifyCalcCtHash and insertCtHash one item at a time.

Fix

zkVerifyCalcCtHashesPacked now calculates item i with salt + i, the salt that item is inserted at. Item 0 and single item batches keep the hashes they had. A batch now gets exactly the hashes the Foundry client produces for the same inputs (checked on Hardhat by comparing zkVerifyCalcCtHashesPacked against zkVerifyCalcCtHash plus insertCtHash per item). The ABI is unchanged, and the chainId argument is passed through as before, so no new compiler warning.

Tests

  • New case in test/hardhat-plugin-test/test/encrypt-inputs.test.ts: encrypts [uint32(9n), uint32(9n)], expects two different hashes, then verifies the batch on chain and decrypts the stored value to 9n. It fails on master (expected '0x464e…' to not equal '0x464e…') and passes with the fix.
  • Full test/hardhat-plugin-test suite: 102 passing and 1 failing (the new case) on master, 103 passing with the fix.
  • Prettier check passes on the changed files.

Changeset

.changeset/mock-zk-verifier-batch-hashes.md, patch for @cofhe/mock-contracts.


Note

Low Risk
Changes are limited to mock verifier hashing for local Hardhat tests; production contracts and ABIs are unchanged.

Overview
Fixes Hardhat mock behavior where encryptInputs batches with duplicate plaintexts (e.g. two uint32(9n)) returned identical ctHashes, so contracts treating handles as distinct inputs saw a single ciphertext.

MockZkVerifier now folds an explicit salt into _calcPlaceholderKey, and zkVerifyCalcCtHashesPacked derives item i with salt + i so precomputed hashes match the salt advanced by insertPackedCtHashes / insertCtHash. Single-input and zkVerify paths still use the current salt, so batch-of-one behavior stays the same and packed results align with per-item Foundry-style verification.

Adds a Hardhat plugin test that asserts two equal batch inputs get different hashes, then batch-verify and decrypt still succeed. Includes a patch changeset for @cofhe/mock-contracts.

Reviewed by Cursor Bugbot for commit 273eb6e. Bugbot is set up for automated code reviews on this repo. Configure here.

zkVerifyCalcCtHashesPacked calculated every item of a batch with the
current salt, and the salt only advances when insertPackedCtHashes
stores the items afterwards. Equal values encrypted in one
encryptInputs call on Hardhat therefore got the same ctHash.

Calculate item i with salt + i, the salt it is inserted at. Item 0 and
single item batches keep their hashes, and a batch now gets the same
hashes the Foundry CofheClient produces by calculating and inserting
one item at a time.
@changeset-bot

changeset-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 273eb6e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@cofhe/mock-contracts Patch
@cofhe/foundry-plugin Patch
@cofhe/hardhat-3-plugin Patch
@cofhe/hardhat-plugin Patch
@cofhe/site Patch
@cofhe/abi Patch
@cofhe/react Patch
@cofhe/sdk Patch
@cofhe/example-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@alexshchur
alexshchur merged commit afeb3c8 into FhenixProtocol:master Nov 2, 2026
13 checks passed

@alexshchur alexshchur left a comment

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.

Thanks @erkancamli, nice catch. On the Hardhat mocks, equal values of the same type in one batch shared a handle. Now each gets its own, same as Foundry already did. Merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants