Skip to content

fix: lossily decode STypeVar name to match the JVM TypeSerializer (UTF-8 leniency, byte-exact U+FFFD)#910

Open
mwaddip wants to merge 2 commits into
ergoplatform:developfrom
mwaddip:fix/stypevar-utf8-lossy
Open

fix: lossily decode STypeVar name to match the JVM TypeSerializer (UTF-8 leniency, byte-exact U+FFFD)#910
mwaddip wants to merge 2 commits into
ergoplatform:developfrom
mwaddip:fix/stypevar-utf8-lossy

Conversation

@mwaddip

@mwaddip mwaddip commented Jun 16, 2026

Copy link
Copy Markdown

Depends on #908 (shares its Vec<u8> name change; #908's commit shows here until it merges — this is the second commit).

The JVM TypeSerializer builds the name via new String(bytes, UTF_8) — lossy: malformed bytes become U+FFFD, parsing never fails. sigma-rust rejected a non-UTF-8 name, a deserialization fork vs sigma-state 6.0.3.

Fix: a JVM-matching lossy decoder storing the canonical form. The U+FFFD substitution matches the JVM byte-for-byte — a whole attempted multibyte sequence collapses to one U+FFFD (surrogate ed a0 80 → one, not the three from_utf8_lossy yields) — so the re-serialized name and script hash stay identical across nodes.

Validated against the JVM oracle (sigma-state 6.0.3) by the conformance suite (accept + round-trip byte-exactness).

mwaddip added 2 commits June 15, 2026 22:00
JVM `TypeSerializer.deserialize` reads the type-var name length via
`getUByte()` (0..=255, no bound) then `new String(getBytes(len))`, so an
empty name (length 0) and a 255-byte name both deserialize. sigma-rust
bounded the name to `BoundedVec<u8, 1, 254>`, over-rejecting length 0 and
255 with a parse error -- a deserialization divergence from sigma-state.

Use `Vec<u8>`: a `get_u8` length is inherently 0..=255, so no bound is
meaningful. The UTF-8 validity check is retained. Adds a regression test
on `STypeVar::sigma_parse` for length 0x00 and 0xff.
…erializer

The JVM `TypeSerializer.deserialize` builds the type-var name via
`new String(bytes, UTF_8)`, which is lossy: malformed bytes become U+FFFD and
the name never fails to parse. sigma-rust rejected a non-UTF-8 name with a
SigmaParsingError -- a deserialization fork vs sigma-state 6.0.3 (the JVM
accepts the tree, sigma-rust rejects it).

Decode with a JVM-matching lossy decoder and store the canonicalized form, so
`new_from_bytes` no longer errors and equality / round-trip match the JVM. The
U+FFFD substitution matches the JVM byte-for-byte: a whole attempted multibyte
sequence collapses to one U+FFFD (e.g. the surrogate `ed a0 80` -> one U+FFFD,
where Rust `from_utf8_lossy` yields three), so the re-serialized name -- and the
script hash -- stay identical across implementations. Tested against SANTA's
oracle table (sigma-state 6.0.3 / Java 17). Stacked on the name-length-bound fix.
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.

1 participant