Skip to content

core: Reject equal sender blinders in sender encryption - #332

Merged
HDauven merged 1 commit into
masterfrom
core/document-distinct-blinders
Oct 16, 2026
Merged

HDauven merged 1 commit into
masterfrom
core/document-distinct-blinders

Conversation

@HDauven

@HDauven HDauven commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Follow-up to a review note on #331: Sender::encrypt and Note::new accepted two equal sender-encryption blinders. Equal blinders give both component ciphertexts the same first coordinate c1, so their second coordinates differ by the sender-fixed A − B, letting a passive observer link the sender (#330).

This rejects equal blinders at runtime, as the existing zero-blinder / non-prime-order degenerate cases already do.

Why enforce, not just document: it is the only check that reaches every sender. The transaction circuit covers the two output notes, but Transaction::new in rusk's dusk-core also encrypts a gas-refund sender that no circuit checks — so a doc-only precondition would leave that sender unguarded.

It branches on no secret: the guard compares the two published c1 points (sender_enc_A.0 == sender_enc_B.0), which go into Sender::Encryption and onto the chain, not the secret blinders. c1 = r·G with G of prime order, so the c1 points are equal exactly when the blinders are, and JubJubAffine equality is constant-time. This resolves the original concern that enforcing would branch on secret data. It is a runtime assert!, not a debug_assert!, since the Makefiles test with --release.

Production is unaffected — the transaction builder draws the two blinders independently.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The requirement is missing from the public convenience constructors that accept the same blinders.

1 open finding
What changed in this PR

Documents the distinct sender-blinder requirement to prevent unlinkability issues and circuit rejection.

Changes:

  • Documents the precondition on Note::new.
  • Explains the security rationale on Sender::encrypt.
File Description
core/​src/​note.rs Adds sender-blinder requirements and rationale.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread core/src/note.rs Outdated
@HDauven
HDauven force-pushed the core/document-distinct-blinders branch from d3235a1 to 81029d2 Compare October 15, 2026 01:09
@HDauven
HDauven marked this pull request as ready for review October 15, 2026 01:09
@HDauven
HDauven requested a review from moCello October 15, 2026 01:09

@moCello moCello left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

APPROVE — The convenience constructors now carry the requirement, and the explanation of the equal-blinder leak agrees with the circuit and with equal_sender_blinders_rejected. The note below answers the question in the description: the guard is worth adding.

Comment thread core/src/note.rs Outdated
Comment on lines +607 to +610
/// This constructor does not enforce distinctness, since comparing the
/// blinders would branch on secret data, so the caller must pass distinct
/// blinders — the transaction builder does so by drawing them
/// independently.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

NOTE: Yes to the guard that the description offers. Sender::encrypt can compare the two c1 points, which the note publishes, so the check branches on no secret.

Context: c1 = r·G, and G has prime order, so the two c1 points are equal exactly when the blinders are. Both points go into Sender::Encryption and onto the chain, and JubJubAffine equality is constant-time. Both constructors already panic on a zero blinder, so the guard adds a case to an existing panic. It is also the only check that reaches every sender. In rusk's dusk-core, Transaction::new also calls Sender::encrypt for the gas refund sender, and no circuit checks that sender. So "the transaction circuit rejects such a note" holds only for the two output notes.

Fix: After the conversion to tuples, panic when sender_enc_A.0 == sender_enc_B.0, as the zero case does. Then:

  • Replace this doc paragraph with the rule and its reason, and add the equal case to # Panics on Sender::encrypt and Note::new.
  • Add a test beside note_panics_on_zero_sender_blinder_for_a.
  • Extend the [Unreleased] changelog entry that lists the zero-blinder panic.

A debug_assert! does not do this job, because the Makefiles test with --release.

@HDauven
HDauven force-pushed the core/document-distinct-blinders branch from 81029d2 to a8b9aa6 Compare October 16, 2026 00:43
@HDauven HDauven changed the title core: Document the distinct-blinder requirement for sender encryption core: Reject equal sender blinders in sender encryption Oct 16, 2026
@HDauven
HDauven merged commit d285279 into master Oct 16, 2026
5 checks passed
@HDauven
HDauven deleted the core/document-distinct-blinders branch October 16, 2026 00:58
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.

3 participants