Skip to content

Candidate for 0.30.0 - #896

Open
kushti wants to merge 16 commits into
developfrom
v0.30.0
Open

Candidate for 0.30.0#896
kushti wants to merge 16 commits into
developfrom
v0.30.0

Conversation

mwaddip and others added 15 commits May 31, 2026 01:34
sigma-state restricts tuples to exactly 2 elements: `Tuple.eval` rejects
`items.length != 2` with "Invalid tuple" (values.scala: "in v5.0 version we
support only tuples of 2 elements to be equivalent with v4.x"). The check is
unconditional (no version gate); sigma-state 6.0.3 is JIT-only and evaluates
every height through it.

sigma-rust models tuples as flat N-ary (`TupleItems = BoundedVec<2,255>`) and
its `Tuple` eval had no arity check, so it accepted arity>=3 tuples the JVM
rejects — a consensus accept/reject divergence: sigma-rust would accept a spend
the JVM rejects (crafter-constructible). Surfaced by the santa eval fixture
`tuple_triple_bool_byte_short` (tree 0086030101020703a413): JVM rejects "Invalid
tuple"; sigma-rust accepted Tuple[true, 7, 1234].

Mirror the JVM: reject `items.len() != 2` at eval. No mainnet block can carry an
arity>=3 tuple (the JVM rejects them), so this cannot regress historical sync.
Regression test added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
powHit computes the Autolykos-2 PoW hit value (a big integer), so its
result type is UnsignedBigInt — matching Scala SGlobalMethods.powHit and
the interpreter's POW_HIT_EVAL_FN, which already yields
Value::UnsignedBigInt. The descriptor mis-declared SBoolean, so
coll.map(x => Global.powHit(..)).exists((u: UnsignedBigInt) => ..) failed
parse-time type-checking ("Invalid condition tpe"), wedging a full node
off testnet at canonical block 28,474.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TransactionContext::validate summed input and output box values with
plain u64 sums, so an overflowing aggregate panicked in debug builds
and wrapped in release builds before BoxValue::new could reject it —
the input check misfired and the output sum had no check at all. The
reference implementation sums with Math.addExact over longs
(ErgoTransaction.validateStateful), trapping every addition.

Sum with i64 try_fold/checked_add — the same idiom validate_stateless
already uses — returning InputSumOverflow/OutputSumOverflow. This also
drops the spurious lower-bound check the BoxValue::new construction
applied to the aggregate (the reference bounds boxes, not the sum).

Closes #881.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tion

The JVM reads the Option DATA tag through getOption (sigma
Extensions.getOption): `if (tag != 0) Some(getValue) else None`. Our
sigma-ser get_option mapped only tag 1 to Some and silently returned None
on any other nonzero tag WITHOUT consuming the value bytes, desyncing the
stream — a v3 tree carrying an SOption constant with DATA tag 0x02 failed
to parse where the JVM evaluates it to Some(5).

Fix the shared reader helper to mirror the convention; put_option still
writes 0/1, so the write side is unchanged. The only other caller
(ergo-p2p peer-spec parsing) aligns with the same scorex reader
convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ContextExtension keys are a signed Byte JVM-side. ErgoLikeContext
.toSigmaContext builds the contextVars as new Array(maxKey+1) indexed by
the signed key (ErgoLikeContext.scala:140-146), so a self-box extension
carrying a key >= 0x80 (signed-negative) crashes context construction
(NegativeArraySizeException / ArrayIndexOutOfBoundsException) BEFORE any
bytecode runs — the spend never validates. The crash is on the key's
PRESENCE, independent of whether the script reads it.

sigma-rust stores extension keys as unsigned u8 and resolves them lazily
at GetVar, so it would otherwise ACCEPT a >= 0x80 key where the JVM
rejects — a consensus fork on the attacker-supplied spending extension
(the node feeds the spending input's extension into ctx.extension before
reduce_to_crypto). Reject any self-extension key outside 0..=127 at the
reduction boundary, mirroring the construction-time crash as a clean
rejection. This is the top-level self extension only; the per-input
getVarFromInput path masks the id with & 0xff by design and is untouched,
and a GetVar of a >= 0x80 id with the key absent still resolves to None.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gate

The JVM `HeaderWithoutPow` reads `version` as a signed `Byte` and gates the
`unparsedBytes` region on `version > 1`. sigma-rust compared `version` as
`u8`, so a version byte 0x80 (-128 signed) was seen as 128 > 1: the parser
consumed `unparsedBytes`, shifting the AutolykosSolution parse and yielding a
different `minerPk` than the JVM (which skips the region) -- a deserialization
fork vs sigma-state 6.0.3.

Compare the signed interpretation `(version as i8) > 1` at the parse and
serialize gates. Versions 1/2/3 are positive, so signed == unsigned; only
version >= 0x80 changes, matching the JVM. The two `version > 1` sites in the
arbitrary proptest generator are left unchanged: it emits only versions {1,2},
and one is a v2-field nulling gate (`!= 1`), not an unparsedBytes gate. Adds a
regression test on the 0x80/0x7f witness headers (minerPk infinity vs real).
fix: Option DATA parser treats any nonzero tag as Some, per JVM getOption
fix: read Header version as a signed Byte at the unparsedBytes gate (JVM parity)
Reject self ContextExtension keys >= 0x80 (JVM context-construction parity)
fix: reject non-pair tuples at eval to match sigma-state consensus
fix(ergotree-ir): Global.powHit returns UnsignedBigInt, not Boolean
…checkType)

The JVM checkTypes each Tuple item (values.scala:801/804) via
SType.isValueOfType, which rejects a tuple type of arity != 2 (and a function
type of arity != 1) with "Unsupported tuple type" (SType.scala:200-205).
sigma-rust models flat N-ary tuples (TupleItems 2..=255), so an arity-3 tuple
carried as a constant (the item of a valid pair) evaluated where the JVM
rejects — a consensus accept/reject divergence.

Add check_value_type mirroring isValueOfType's reachable rejections and call it
at the Tuple-eval items. Placeholder constants are substituted into the body
before eval on this branch, so the segregated case reduces to the same inline
Tuple-item case; the eni cherry-pick additionally guards ConstantPlaceholder
eval (where eni resolves placeholders lazily).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the JVM order: checkType before evaluating each item
(values.scala:801/804). Aborts earlier if the type is unsupported
rather than doing the eval work first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: reject unsupported (arity != 2) tuple-typed values at eval (JVM checkType)
@kushti
kushti requested a review from sethdusek July 9, 2026 15:32
fix: checked ERG summation in stateful transaction validation
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.

2 participants