fix(security): bind burn cipher nonce to nullifier#25
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughImplements v2 burn-message encryption for shielded TRC-20 using a nonce derived from the note nullifier (nf), updates encrypt/decrypt signatures to accept nf and a nonce-from-log, integrates OVK into the parameters builder, propagates pending nullifiers during Wallet log scanning, and adds comprehensive unit tests. ChangesShielded TRC-20 V2 Burn-Message Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="framework/src/main/java/org/tron/core/zen/note/NoteEncryption.java">
<violation number="1" location="framework/src/main/java/org/tron/core/zen/note/NoteEncryption.java:372">
P2: Treating `null` as all-zero silently falls back to insecure v1 decryption. If a caller fails to extract the nonce from the log and passes `null`, the nonce verification is bypassed entirely — undermining the replay protection this fix adds. Consider returning `false` for null (or throwing) so that a missing nonce is never silently accepted as a v1 record.</violation>
</file>
<file name="framework/src/test/java/org/tron/core/zen/note/BurnCipherV2Test.java">
<violation number="1" location="framework/src/test/java/org/tron/core/zen/note/BurnCipherV2Test.java:86">
P2: This test should assert rejection for the bad address prefix. As written, it asserts decryption succeeds and only checks the raw byte value, so it would still pass if invalid prefixes were accepted.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
460ea93 to
a8442b9
Compare
5de2e31 to
39f6829
Compare
fa0bc20 to
9b2c9ae
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java`:
- Around line 298-310: Validate transparentToAddress before copying in the OVK
burn-encrypt path: inside the block guarded by ovk != null (the code that
creates addr21 and calls Encryption.encryptBurnMessageByOvk), check that
transparentToAddress is non-null and has length >= 20 (same guard as
ArrayUtils.isEmpty(transparentToAddress) used elsewhere); if it fails, throw a
clear ZksnarkException (or skip encryption explicitly) so a meaningful error is
raised instead of letting System.arraycopy throw
NullPointerException/ArrayIndexOutOfBoundsException. Ensure the check is placed
before creating addr21 or calling Encryption.encryptBurnMessageByOvk so the
error context remains tied to build()/ovk handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 29c7f520-6cb7-4e57-a0ea-02eeddd7b8ae
📒 Files selected for processing (4)
framework/src/main/java/org/tron/core/Wallet.javaframework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.javaframework/src/main/java/org/tron/core/zen/note/NoteEncryption.javaframework/src/test/java/org/tron/core/zen/note/BurnCipherTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- framework/src/main/java/org/tron/core/Wallet.java
c9b5c16 to
7f7e289
Compare
2648e05 to
682d8d5
Compare
682d8d5 to
683cd6c
Compare
683cd6c to
e9088da
Compare
What does this PR do?
Hardens shielded TRC-20 burn encryption by binding the burn AEAD nonce to the spend nullifier, storing the nonce in the burn record, and making the burn-record version explicit in the trailing reserved bytes.
Why are these changes required?
The original implementation encrypted every burn payload under the same long-lived
ovkwith a static all-zero ChaCha20-Poly1305 nonce. Reusing(key, nonce)pairs breaks the AEAD security assumptions and leaks keystream relationships across burns.This PR gives each burn a nonce derived from its nullifier and records enough metadata on chain for scanners to distinguish legacy and upgraded burn records without relying on ambiguous zero-nonce heuristics.
Design
Nullifier-bound nonce with domain separation
The fixed domain tag prevents collisions with any other SHA3-of-nullifier use. Since the nullifier is unique per spend, the resulting
(ovk, nonce)pair is also unique per burn.96-byte burn record
encryptBurnMessageByOvkreturns a 96-byte record that occupies the same calldata / log slot as the previouscipher(80) || zero-padding(16)layout:Current version marker:
Explicit legacy/v2 discrimination for scanners
decryptBurnMessageByOvk(ovk, cipher, nonceFromLog, reservedFromLog, nf)now treats the trailing 4 reserved bytes as an explicit version marker:reserved = 0x00000000andnonce = 0x000...000-> legacy v1 pathreserved = 0x00000001-> v2 path; the scanner must verifynonce == SHA3(domain || nf)[:12]reservedvalue -> rejectThis removes the previous ambiguity where an all-zero nonce could be interpreted both as a valid new record and as the legacy sentinel.
Encryption moved into the builder
BURN encryption now runs inside
ShieldedTRC20ParametersBuilder.build()aftergenerateSpendProof(...), once the nullifier is available. Callers only need to provideovk; if omitted, the builder falls back tospend.expsk.getOvk().Compatibility
toAddress 32 + value 32 + burn record 96)cipher(80) + nonce(12) + reserved/version(4)replacescipher(80) + zero padding(16)for newly generated burnsreserved=0 && nonce=0)getTriggerInputForShieldedTRC20ContractKey changes
NoteEncryption.javaBURN_CIPHER_RECORD_SIZE=96, explicit reserved/version marker handling, nonce derivation from nullifier, and strict v1/v2 burn-record decryption rulesShieldedTRC20ParametersBuilder.javaWallet.javacipher(80)+nonce(12)+reserved/version(4); scanner now uses explicit version markers; 80-byte trigger-input burn ciphers throw a clear deprecation/rejection errorBurnCipherTest.javaNoteEncDecryTest.javaTesting
./gradlew framework:test --tests "org.tron.core.zen.note.BurnCipherTest"./gradlew framework:test --tests "org.tron.core.zksnark.NoteEncDecryTest"./gradlew framework:test --tests org.tron.core.zksnark.NoteEncDecryTest.testGetTriggerInputBurn80ByteCipherRejected