Repository navigation
fix(js): base confidential-mint supply on the confidential ciphertext - #1402
Conversation
The mint helper committed the new supply as `decryptable_supply + amount` while the equality-proof ciphertext encrypts `confidential_supply + amount`. CiphertextCommitmentEqualityProof requires both values to match, so any confidential mint fails on-chain once the two supplies diverge: after an apply-pending-burn advances the encrypted supply without re-encrypting the decryptable one, or when InitializeMintData sets a nonzero decryptable supply. Derive the new supply from the confidential ciphertext via the supply ElGamal keypair, which also keeps the decryptable supply in sync. Adds a regression test that mints, burns, applies the pending burn, and mints again without a manual decryptable-supply re-sync.
|
Update: after reproducing the audit locally, the advisory that actually fails the job is RUSTSEC-2026-0258 (
This is unrelated to the changes in this PR and would fail on Pushed 1feea7b adding both advisories to the ignore list in the Makefile Verified locally that |
RUSTSEC-2026-0258 (h2 unbounded empty DATA frames) is the advisory that fails the Audit job. h2 0.3.x has no fixed release, the fix requires the 0.4 line, so ignore it the same way as the other advisories without an applicable patch. RUSTSEC-2026-0173 (proc-macro-error2 unmaintained) has no replacement, ignore it as well to keep the job green.
|
The public helper docs still tell callers to manually re-sync the supply after The obsolete warning and workaround should be removed from the JSDoc. |
|
@samkim-crypto what do you think about this flow? Are people expected to always call |
|
@joncinque I am currently unwell and unable to attend to anything at the moment. I am replying to this from my phone. sorry 🙏 |
|
The callers are not expected to re-sync after every It seems like the JS client never got the helper, which is why the docs tell people to re-sync manually without saying how to get the number, so this does need addressing. But the ElGamal decryption in zk-sdk is bounded to 32-bit plaintexts, and the supply is a @latent-9 Can you port the reconciliation mechanism from here instead? Concretely, I think we can add a |
|
Hi @latent-9, thanks again for this contribution! I think it's a great change and I'd love to get it merged. Are you still interested in working on it? If I don't hear back or see updates within a week, I'll either take it over and finish it up (keeping your commits so you're credited) or close it for now. Either way, your work here is appreciated! |
|
Hi @samkim-crypto, thanks for taking a look and sorry for the slow replies -- yes, still very interested in getting this merged! You're right about the 32-bit bound: decrypting |
… on-chain ciphertext
ElGamal decryption in zk-sdk is bounded to 32-bit plaintexts, so decrypting
the u64 confidential_supply directly fails for any mint above 2^32 base
units. Port the reconciliation mechanism from the rust helpers instead:
AES-decrypt the cached decryptable supply, homomorphically subtract the
on-chain supply ciphertext, and decrypt only the difference (the burns
since the last sync) as
decryptConfidentialMintBurnSupply({ mintAccount, supplyElgamalKeypair, supplyAesKey }).
buildConfidentialMintProofPlan uses it, so the decryptable supply re-syncs
for free and the manual re-sync workaround is unnecessary. Adds a test that
mints above 2^32, burns, applies, and mints again without a re-sync.
samkim-crypto
left a comment
There was a problem hiding this comment.
Just one minor docs wording comment. After this, I'll approve and merge!
| * ElGamal decryption is only practical for small plaintexts, so the full supply | ||
| * ciphertext is never decrypted directly. Instead the cached supply is | ||
| * re-encrypted under the supply ElGamal key and homomorphically subtracted from | ||
| * the on-chain ciphertext; decrypting that difference yields only the burns | ||
| * applied since the last supply sync, and the current supply is the cached | ||
| * value minus those burns. |
There was a problem hiding this comment.
I think this documentation is describing the subtraction backwards. Can we maybe say:
* The SDK's ElGamal decryption is limited to 32-bit plaintexts, so the full
* supply ciphertext is never decrypted directly. Instead, the cached supply
* is re-encrypted under the supply ElGamal key, and the on-chain supply
* ciphertext is homomorphically subtracted from it:
* `Enc(cachedSupply) - confidentialSupply`. Decrypting this difference yields
* the burns applied since the last supply sync, which must fit in 32 bits.
* The current supply is the cached supply minus those burns.
The subtraction description read backwards. Reword it the way samkim-crypto suggested: the on-chain supply ciphertext is homomorphically subtracted from the re-encrypted cached supply.
samkim-crypto
left a comment
There was a problem hiding this comment.
Thanks for the changes!
Summary
buildConfidentialMintProofPlanderives the new supply from the AESdecryptable_supply, but pairs the resulting commitment with a ciphertextthat encrypts
confidential_supply + amount. TheCiphertextCommitmentEqualityProofonly verifies when the ciphertext and thecommitment encode the same value, so the mint is rejected on-chain whenever
the two supplies differ:
ApplyPendingBurnadvances the ElGamalconfidential_supplybut does not re-encrypt the AESdecryptable_supply.InitializeMintData.decryptable_supplydiverges from the zero confidential supply before the first mint.
The on-chain mint hard-requires the new supply ciphertext to equal
add_with_lo_hi(confidential_supply, amount)and verifies the equality proofagainst that value, so the only consistent plaintext for the commitment is
the confidential supply, not the decryptable one.
Change
Derive
currentSupplyby decryptingconfidentialSupplywith the supplyElGamal keypair. The resulting
newDecryptableSupplyalso re-syncs the AESdecryptable supply in the same instruction, matching the manual
getUpdateConfidentialMintBurnDecryptableSupplyInstructionFromSupplycallthat was previously required after every burn.
Test
Adds a regression test that mints, burns, applies the pending burn (without
any manual decryptable-supply re-sync), and mints again. It verifies the
second mint succeeds and that the decryptable supply tracks the encrypted
supply.