Skip to content

Check the subgroup of points decoded through Serde - #326

Open
HDauven wants to merge 1 commit into
phoenix/dhke-ownershipfrom
phoenix/serde-subgroup
Open

HDauven wants to merge 1 commit into
phoenix/dhke-ownershipfrom
phoenix/serde-subgroup

Conversation

@HDauven

@HDauven HDauven commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Stacked on #325.

When a Note or Sender is deserialized with Serde, its value commitment and sender-encryption points now go through Serializable::from_bytes, so they get the same subgroup check as the dusk-bytes decoders. Each point must still be exactly 32 bytes. The JSON encoding is unchanged, and the existing JSON vectors still decode. dusk-network/jubjub#192 tracks the same alignment in dusk-jubjub's own Deserialize.

Resolves #318

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.

🟢 Approval recommended

The implementation consistently reuses canonical decoding and covers all affected point fields with focused tests.

0 open findings

What changed in this PR

Aligns Serde point decoding with canonical dusk-bytes subgroup validation for Note and Sender.

Changes:

  • Adds subgroup-validating point deserializers.
  • Applies them to value commitments and sender encryptions.
  • Adds rejection and compatibility tests plus a changelog entry.
File Description
core/​src/​serde_support.rs Adds canonical point deserialization helpers.
core/​src/​note.rs Applies validation to relevant point fields.
core/​tests/​serde.rs Tests invalid subgroup rejection and valid decoding.
core/​CHANGELOG.md Documents the Serde validation fix.

🧠 Review effort: Balanced


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

@HDauven
HDauven force-pushed the phoenix/serde-subgroup branch from d9c5f3d to 4c2d3ab Compare October 10, 2026 23:13
@HDauven

HDauven commented Oct 10, 2026

Copy link
Copy Markdown
Member Author

Force-pushed: rebased on the amended stack. No changes.

@HDauven
HDauven force-pushed the phoenix/serde-subgroup branch from 4c2d3ab to 433b322 Compare October 11, 2026 01:32
@HDauven

HDauven commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

Force-pushed: rebased on the amended stack. No changes.

@HDauven
HDauven marked this pull request as ready for review October 11, 2026 02:02

@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.

COMMENT — Every point of a note and a sender encryption now goes through the subgroup check, and the new test fails on the old decoder. Approval waits on one fix: the point decoder must reject a point encoding that is longer than 32 bytes.

Comment thread core/src/serde_support.rs
Comment on lines +25 to +31
pub(crate) fn deserialize_point<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<JubJubAffine, D::Error> {
let bytes: Vec<u8> = As::<Hex>::deserialize(deserializer)?;
JubJubAffine::from_slice(&bytes)
.map_err(|err| SerdeError::custom(format!("{err:?}")))
}

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.

CAUTION: The new point decoder accepts a hex string longer than 32 bytes and ignores the extra bytes, which the replaced Deserialize of JubJubAffine rejected.

Context: DeserializableSlice::from_slice fails only when the input is shorter than N. It copies the first N bytes and drops the rest. The Deserialize of AffinePoint in dusk-jubjub decodes the hex into [u8; 32] and rejects any other length. The key and stealth-address decoders in this file do the same with try_into, and serde_too_long_encoded asserts that rule for them.

Impact: A value_commitment or sender point written as the 64 honest hex digits plus any whole number of extra hex-encoded bytes now decodes to the same Note. serde_rejects_points_outside_the_subgroup does not test the length.

Fix: Decode into a fixed array, as value_enc does: let bytes: [u8; JubJubAffine::SIZE] = As::<Hex>::deserialize(deserializer)?;. Then call the trait method by its full path: <JubJubAffine as Serializable<32>>::from_bytes(&bytes). Do not write JubJubAffine::from_bytes, because that path resolves to the inherent method, which skips the subgroup check. Add a test that appends one byte to an honest point and expects an error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done as you describe. The point decodes into [u8; JubJubAffine::SIZE], then goes through <JubJubAffine as Serializable<32>>::from_bytes, with a comment that JubJubAffine::from_bytes would resolve to the inherent decoder and skip the subgroup check. A new test adds a byte to each point, then removes one, and checks that neither the note nor the sender decodes. On the previous head, all five points decoded with the extra byte dropped. The other decoders in the file already require exact sizes.

The value commitment of a `Note` and the points of a `Sender` encryption
derive their Serde implementation from dusk-jubjub, whose `Deserialize`
checks that a point is on the curve, but not, as `Serializable::from_bytes`
does, that it lies in the prime-order subgroup (dusk-network/jubjub#192).
A note or sender decoded from JSON could carry a point its dusk-bytes
decoders reject.

Decode those points through `Serializable::from_bytes`, as the Serde
implementations of the keys and the stealth address already do through
their dusk-bytes decoders. A point must still be exactly 32 bytes. The
encoding is unchanged, and the existing JSON vectors still decode.

The test replaces the value commitment and each sender point of an
honest note with the order-two point, then with the generator plus it,
and checks that neither the note nor the sender decodes, while the
honest note, and the empty note, whose points are the identity,
round-trip. Another test adds a byte to each point, then removes one,
and checks that neither the note nor the sender decodes.
@HDauven
HDauven force-pushed the phoenix/serde-subgroup branch from 433b322 to e059e38 Compare October 11, 2026 17:43
@HDauven
HDauven requested a review from moCello October 11, 2026 17:44
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.

Check the subgroup of points decoded through Serde

3 participants